Async processing issue within an event handler
James Lynes <[email protected]> Mon, 21 Sep 2015 22:43:31 -0400
| Newsgroups | gmane.comp.lang.perl.wxperl |
|---|---|
| Message-ID | <CABPysehy=T2-sdvekw57NAcYM_epaFYRy+b2_2MGCYJu0dCzxQ@mail.gmail.com> |
I apologize for the length of this question in advance.
Background: The July 2015 issue of Nuts and Volts magazine had an article
on using an inexpensive DVB-T 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 sudo add-apt-repository
ppa:gqrx/snapshots, a stable version was found.
Well, gqrx has a Remote Control feature that can accept commands 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(wxgqrxLite.pl), mostly a bunch of text controls and sizers.
Problem 1: lite.pl is basically a while(1) loop scanning from F1 to F2,
listening for a second when a strong signal is detected.
Question 1: Where should the non-GUI stuff fit into a wxPerl application?
Current Solution 1: I created two timer events and put the scan code in one
and the listen code in the other. They trigger each other alternately at
separate rates(30msec and 2 seconds). Only one of these events/timers are
active at a time. This actually works!
Problem 2: You can't start scanning unless the Telnet connection is open,
otherwise it throws a connection refused error.
Solution 2: Add code to track scanning and connection status to prevent the
user from doing illegal operations or multiple consecutive hits of the same
button, and popup message boxes to flag these errors.
Problem 3: The error checks, interlocks and message boxes between the
{startbutton} and {stopbutton} events seems to be ok. The same cannot be
said for the {connectbutton} and {disconnectbutton} events. There seems to
be a race/async processing problem between establishing the Telnet
connection(which errors and should set the {tnerror} flag) and the mainline
event code. The three individual print Dumper $self->{tnerror} statements
all show {tnerror} = 0 (wrong!) while the print Dumper $self statement
below shows {tnerror} = 1 (right!). There's a delay in this flag being set
in the Net::Telnet call which causes the rest of the {connectbutton} event
routine to process a wrong flag value.
Question 3: HELP ????? I'm out of ideas...
Question 4: Does event->Skip() terminate the current event handler like
last terminates a while loop?
Code attached. It will run without the dongle or gqrx installed. It just
fails on the Telnet connection.
Thanks for your input.
James
Ubuntu 14.04.2LTS/perl 5.18.2/wxPerl 3.0.1/HP 15 Quadcore Laptop
lite.pl
(application/x-perl, 2.1 KB)
#! /usr/bin/perl
# Name: lite.pl - Simple gqrx SDR Scanner
# Author: James M. Lynes, Jr.
# Created: September 17, 2015
# Modified By: James M. Lynes, Jr.
# Last Modified: September 17, 2015
# Change Log: 9/17/2015 - Program Created
# Description: Simple interface to gqrx to implement a SDR scanner function using the
# Remote Control feature of gqrx(small subset of rigctrl protocol)
#
# gqrx is a software defined receiver powered by GNU-Radio and QT
# Developed by Alex Csete - gqrx.dk
#
# This code is inspired by "Controlling gqrx from a Remote Host" by Alex Csete
# and the gqrx-scan scanner code 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
#
#
use strict;
use warnings;
use Net::Telnet;
use Time::HiRes;
# Defines
my $ip = "127.0.0.1";
my $port = "7356";
my $step = 1000;
my $begin = 162400000;
my $end = 162600000;
my $mode = "FM";
my $pause = .005;
my $listen = 1;
my $squelch = -60;
# Open Telnet connection to gqrx via localhost::7356
my $t = Net::Telnet->new(Timeout=>2, port=>$port);
$t->open($ip);
# Set the demodulator type
$t->print("M $mode");
$t->waitfor(Match=> '/RPRT', Timeout=>5, Errmode=>"return");
# Set up to run the scan cycle count times
my $count = 0;
while($count < 10) {
my $start = $begin;
while($start <= $end) {
$t->print("F $start"); # Set Frequency
$t->waitfor(Match=> '/RPRT', Timeout=>5, Errmode=>"return");
$t->print("l"); # Get RSSI (-##.# format)
my($prematch, $level) = $t->waitfor(Match => '/-{0,1}\d+\.\d/', Timeout => 5, Errmode => "return");
if(!defined($level)) {next};
# print $level;
if($level > $squelch) {Time::HiRes::sleep($listen)}; # Pause scan when signal detected
$start = $start + $step;
Time::HiRes::sleep($pause); # Pause before next frequency hop
}
$count++; # Loop for next scan cycle
}
# Close the Telnet connection
$t->print("c");
wxgqrxLite.pl
(application/x-perl, 12.3 KB)
#! /usr/bin/perl
# Name: wxgqrxLite.pl - Simple gqrx SDR Scanner - wxPerl Version
# Author: James M. Lynes, Jr.
# Created: September 17, 2015
# Modified By: James M. Lynes, Jr.
# Last Modified: September 20, 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
# Description: Simple interface to gqrx to implement a SDR scanner function using the
# Remote Control feature of gqrx(small subset of rigctrl protocol)
#
# gqrx is a software defined receiver powered by GNU-Radio and QT
# Developed by Alex Csete - gqrx.dk
#
# This code is inspired by "Controlling gqrx from a Remote Host" by Alex Csete
# and the gqrx-scan scanner code 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.
# An error window will popup if gqrx is not running with Remote Control enabled
# when a connection request is made.
#
package main;
use strict;
use warnings;
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);
use Data::Dumper;
use Net::Telnet;
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "gqrx Lite Scanner", wxDefaultPosition, wxDefaultSize);
# Defines
$self->{ip} = "127.0.0.1"; # AKA Localhost Address
$self->{port} = "7356";
$self->{start} = 0;
$self->{error} = 0;
$self->{tnerror} = 0;
$self->{connected} = 0;
# Create Title Text
$self->{titletext} = Wx::StaticText->new($self, -1, "gqrx Lite Scanner", wxDefaultPosition, wxDefaultSize);
# Create Modulators 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, "Modulators", 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", wxDefaultPosition, wxDefaultSize);
$self->{bftext} = Wx::TextCtrl->new($self, -1, "144000000", wxDefaultPosition, wxDefaultSize);
$self->{eflabel} = Wx::StaticText->new($self, -1, "Ending Frequency", wxDefaultPosition, wxDefaultSize);
$self->{eftext} = Wx::TextCtrl->new($self, -1, "144100000", wxDefaultPosition, wxDefaultSize);
$self->{fslabel} = Wx::StaticText->new($self, -1, "Frequency Step", 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);
# Sizer Structure
# 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);
# Create Timers - Used to flip between scan and listen modes
$self->{ptimer} = Wx::Timer->new($self); # Sets the scan cycle time
$self->{ltimer} = Wx::Timer->new($self); # Sets the signal listen time
# Define Event Handlers
Wx::Event::EVT_BUTTON($self, $self->{startbutton}, sub {
my ($self, $event) = @_;
if(!$self->{connected}) { # Can't start scaning if not connected
Wx::MessageBox("Telnet is not connected\nCannot start scanning",
"Telnet Connection Error", wxICON_ERROR, $self);
} else {
$self->{started} = 1;
$self->{beginf} = $self->{bftext}->GetValue; # Copy in working data
$self->{endf} = $self->{eftext}->GetValue;
$self->{step} = $self->{fstext}->GetValue;
$self->{squelch} = $self->{sltext}->GetValue;
$self->{pause} = $self->{sptext}->GetValue;
$self->{listen} = $self->{lptext}->GetValue;
$self->{mode} = $self->{modbox}->GetStringSelection;
$self->{start} = $self->{beginf};
$self->{startbutton}->SetLabel("Scanning"); # Change button label to indicate status
$self->{ptimer}->Start($self->{pause}); # Start the scan cycle
}});
Wx::Event::EVT_BUTTON($self, $self->{stopbutton}, sub {
my ($self, $event) = @_;
if(!$self->{connected}) { # Can't stop scanning if not connected
Wx::MessageBox("Telnet is not connected\nCannot stop scanning",
"Telnet Connection Error", wxICON_ERROR, $self);
} else {
$self->{started} = 0;
$self->{startbutton}->SetLabel("Start Scanning"); # Restore button label
$self->{ptimer}->Stop; # Stop scan cycle
$self->{ltimer}->Stop; # Stop listen cycle
}});
Wx::Event::EVT_BUTTON($self, $self->{connectbutton}, sub {
my ($self, $event) = @_;
print Dumper $self->{tnerror};
$self->{telnetsession} = Net::Telnet->new(Timeout => 2, port => $self->{port},
Errmode => sub { $self->{tnerror} = 1; });
print Dumper $self->{tnerror};
sleep(5);
if($self->{tnerror}) {$self->{error} = Wx::MessageBox(
"gqrx is probably not running\nStart gqrx then restart wxgqrxLite",
"Telnet Connection Error", wxICON_ERROR, $self);}
if(! $self->{tnerror}) { # Can't complete connection sequence
print Dumper $self->{tnerror};
$self->{telnetsession}->open($self->{ip});
$self->{connectbutton}->SetLabel("Connected"); # Change button label to indicate status
$self->{connected} = 1;
}
print Dumper $self;
});
Wx::Event::EVT_BUTTON($self, $self->{disconnectbutton}, sub {
my ($self, $event) = @_;
print "D\n";
if(!$self->{connected}) {
$self->{error} = Wx::MessageBox("Telnet is not connected\nCannot disconnect",
"Telnet Connection Error", wxICON_ERROR, $self);
} else { # Can't complete disconnection sequence
$self->{telnetsession}->print("c");
$self->{connectbutton}->SetLabel("Connect"); # Restore button label
$self->{connected} = 0;
# $self->{error} = 0;
}});
Wx::Event::EVT_BUTTON($self, $self->{quitbutton}, sub {
my ($self, $event) = @_;
$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->{ptimer}, sub { # Scan Mode
my ($self, $event) = @_;
$self->{telnetsession}->print("F $self->{start}"); # Set frequency
$self->{telnetsession}->waitfor(Match => 'RPRT', Timeout => 5, Errmode => "return");
$self->{telnetsession}->print("l"); # Get RSSI -##.#
my ($prematch, $rssi) = $self->{telnetsession}->waitfor(Match => '/-{0,1}\d+\.\d/',
Timeout => 5, Errmode => "return");
if(!defined($rssi)) {$event->Skip()}; # Occasional bad read
if($rssi >= $self->{squelch}) { # Got a strong signal
$self->{ptimer}->Stop; # Stop scan
$self->{ltimer}->Start($self->{listen}); # Start listen
$self->{rssitext}->SetValue($rssi); # Display RSSI value
}
$self->{start} = $self->{start} + $self->{step}; # Increment the frequency
if($self->{start} >= $self->{endf}) {$self->{start} = $self->{beginf}}; # Wrap back around
});
Wx::Event::EVT_TIMER($self, $self->{ltimer}, sub { # Listen mode - Runs once per RSSI trigger
my ($self, $event) = @_;
$self->{ltimer}->Stop; # Stop listen mode
$self->{ptimer}->Start($self->{pause}); # Restart scan mode
});
# Assign mainSizer to the Frame and trigger layout
$mainSizer->Fit($self);
$mainSizer->Layout();
return $self;
}
1;