SDR App working
James Lynes <[email protected]> Tue, 20 Oct 2015 10:21:54 -0400
| Newsgroups | gmane.comp.lang.perl.wxperl |
|---|---|
| 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;