SDR App working

[email protected] (James Lynes)
Newsgroups perl.wxperl.users
Message-ID <CABPysei+2QOHkRvOnqpEvTUi3yDfzY_EY0Spd82gEQLYcC=Fog@mail.gmail.com>
Hi Steve:

After putting it away for awhile, I got the Threaded wxPerl SDR application
working yesterday.

Among other problems, I had a real DUH moment.

You can't type $commom{pause} and expect the compiler to figure out that I
meant $common{pause}! I don't know how many times I looked past the m for n
swap. The compiler was telling me, but I wasn't listening. ☺

Thanks for your suggestions.

James
threadedgqrxLite.pl (application/x-perl, 16.3 KB)
#! /usr/bin/perl

# Name:			threadedgqrxLite.pl - Simple gqrx SDR Scanner - wxPerl 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, Sizers and Event Handlers
#				  - Scan and Listen Timers, Button Label Change as Status Indicator
#			9/20/2015 - Additional Comments, add additional error checking/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 flags
#				  - Add error message timer/event
#			9/29/2015 - Set error message timer to 1 sec
#				  - Redesign threads, collapse to 1 Telnet Server thread
#				  - Object sharing not easily supported by Thread implementation
#			10/19/2015- Fixed scanning loop in Telnet Thread
#				  - Scaled {pause} and {listen} to be in msecs
#                                 - Scaled frequency values to KHz from Hz
#				  - Added Modulation Mode setting
# Description:		"Simple" interface to gqrx to implement a Software 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 provide 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-scan
#
#			Net::Telnet is not in the perl core and was installed from cpan
#			sudo cpanm Net::Telnet
#
# 			Start gqrx and the gqrx remote control option before running 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 Remote Control enabled,
#			or if the dongle is not plugged in, when a Telnet connection request is made.
#
# Notes:		To change parameters: Stop Scanning, Change Parameters, Start Scanning. The
#			previous frequency range will complete scanning before the 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 Wx 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 counter)
    $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 signal - 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 LNA and Audio gains are set.\n";
    while(1) {

        if($common{stopthreads}) {print "\nTelnet Server Thread Terminated\n"; return};

        if($common{connect}) {						# Process Connect Command
            if(!$common{connected}) {
               print "Open Telnet Connection to gqrx\n";
               $telnetsession = Net::Telnet->new(Timeout => 2, port => $common{port},
                                        Errmode => sub {$common{tnerror} = 1;});
               $telnetsession->open($common{ip});

               $telnetsession->print("M $common{mode}");		# Set the demodulator type
               $telnetsession->waitfor(Match=> '/RPRT', Timeout=>5, Errmode=>"return");

               $common{connected} = 1;
               $common{connect} = 0;
            }
        }

        if($common{disconnect}) {					# Process Disconnect 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 Command

                $common{scanstarted} = 1;
                $telnetsession->print("F $common{nextf}");		# Update 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 => "return");

                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 threads 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", wxDefaultPosition, 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", wxDefaultPosition, wxDefaultSize,
                      $modulators, 3, wxRA_SPECIFY_COLS);    

# Create Buttons
    $self->{startbutton}      = Wx::Button->new($self, -1, "Start Scanning", wxDefaultPosition, wxDefaultSize);
    $self->{stopbutton}       = Wx::Button->new($self, -1, "Stop Scanning", 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", wxDefaultPosition, wxDefaultSize);

# Create Data Entry Prompts and Boxes
    $self->{bflabel} = Wx::StaticText->new($self, -1, "Beginning Frequency, KHz", wxDefaultPosition, wxDefaultSize);
    $self->{bftext} = Wx::TextCtrl->new($self, -1, "144000", wxDefaultPosition, wxDefaultSize);

    $self->{eflabel} = Wx::StaticText->new($self, -1, "Ending Frequency, KHz", wxDefaultPosition, wxDefaultSize);
    $self->{eftext} = Wx::TextCtrl->new($self, -1, "144100", wxDefaultPosition, wxDefaultSize);

    $self->{fslabel} = Wx::StaticText->new($self, -1, "Frequency Step, Hz", wxDefaultPosition, wxDefaultSize);
    $self->{fstext} = Wx::TextCtrl->new($self, -1, "1000", wxDefaultPosition, wxDefaultSize);

    $self->{sllabel} = Wx::StaticText->new($self, -1, "Squelch Level", wxDefaultPosition, wxDefaultSize);
    $self->{sltext} = Wx::TextCtrl->new($self, -1, "-60.0", wxDefaultPosition, wxDefaultSize);

    $self->{splabel} = Wx::StaticText->new($self, -1, "Scan Pause, ms", wxDefaultPosition, wxDefaultSize);
    $self->{sptext} = Wx::TextCtrl->new($self, -1, "20", wxDefaultPosition, wxDefaultSize);

    $self->{lplabel} = Wx::StaticText->new($self, -1, "Listen Pause, ms", wxDefaultPosition, wxDefaultSize);
    $self->{lptext} = Wx::TextCtrl->new($self, -1, "1000", wxDefaultPosition, wxDefaultSize);

    $self->{rssilabel} = Wx::StaticText->new($self, -1, "RSSI", wxDefaultPosition, wxDefaultSize);
    $self->{rssitext} = Wx::TextCtrl->new($self, -1, "0", wxDefaultPosition, 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 errors 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 scaning if not connected
			      Wx::MessageBox("Telnet is not connected\nCannot start 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 scanning 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 disconnect if not connected
			      Wx::MessageBox("Telnet is not connected\nCannot Disconnect",
			      "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 Scanning");	# 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");	# Restore 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;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.