#!/usr/bin/perl

use strict;
use Time::HiRes qw(usleep gettimeofday tv_interval);
use Data::Dumper;
use IO::File;
use DynaLoader;

our $VERSION = "0.3";

our %opts;
our %defopts = (
		s => '14.150',
		e => '14.350',
		i => '.0001',
		u => '.1',
		samples => '1',
		c => ($ENV{'HOME'} . "/.cqrc"),
		l => -12,
		C => 1,

		time => 10,
		hangtime => 3,
		keephistory => 600,
		scanmax => 10000,
	       );

#
# ensure they have the Hamlib perl module
#
my $temp_dir = ( $ENV{TEMP} || $ENV{TMP} || $ENV{WINDIR} || '/tmp' ) . "/p2xtmp-$$";
chdir($temp_dir);
my $havehamlib = 
  eval {
#       push @INC, "$temp_dir";
#       push @DynaLoader::dl_library_path, "$temp_dir";
#       $ENV{'LD_RUN_PATH'} = "$temp_dir";
      require Hamlib;
  };
Die("You need to intsall the Hamlib perl module before continuing ($@)")
  if (!$havehamlib);

import Hamlib;

#
# global variables we'll need (ewwwwww)
#
our %config;
our %groups;
our @skips;
# scanning vars
our ($currentchannel, $currentstart, $currentlivetime, @toscan, @pastchannels);
our $locked = 0;

our %modes =
  (
   'USB' => $Hamlib::RIG_MODE_USB,
   'LSB' => $Hamlib::RIG_MODE_LSB,
   'FM' => $Hamlib::RIG_MODE_FM,
   'NFM' => $Hamlib::RIG_MODE_FM,
   'WFM' => $Hamlib::RIG_MODE_WFM,
   'AM' => $Hamlib::RIG_MODE_AM,
   'CW' => $Hamlib::RIG_MODE_CW,
   'CWR' => $Hamlib::RIG_MODE_CWR,
   'RTTY' => $Hamlib::RIG_MODE_RTTY,
   'RTTYR' => $Hamlib::RIG_MODE_RTTYR,
   'PKTLSB' => $Hamlib::RIG_MODE_PKTLSB,
   'PKTUSB' => $Hamlib::RIG_MODE_PKTUSB,
   'PKTFM' => $Hamlib::RIG_MODE_PKTFM,
   'ECSSUSB' => $Hamlib::RIG_MODE_ECSSUSB,
   'ECSSLSB' => $Hamlib::RIG_MODE_ECSSLSB,
  );

#
# read the command line options (and provide help output)
#
LocalGetOptions(\%opts,
	   ["GUI:separator",     "Scanning Options:"],
	   ["s|start-frequency=s", "Starting Frequency"],
	   ["e|end-frequency=s",   "Ending Frequency"],
	   ["i|sample-interval=s", "Steps between frequencies"],
	   ["samples=s",           "Samples to take"],
	   ["l|minimum-level=s",   "Minimum signal level to be reported"],

	   ["state=s",             "store state in file"],
           ["load=s",              "load state from file; don't rescan"],

	   ["scan=s",              "Scan this definition set"],
	   ["u|usleep=s",          "time between sleeps"],
	   ["hangtime=s",          "time in seconds to hang on an empty channel after traffic stops"],
	   ["C|scancount=s",       "Number of stations to scan by default at once before returning to the locked one."],

	   ["GUI:separator",   "Other Commands:"],
	   ["set=s",           "Set a channel directly by name and quit"],
	   ["list=s",           "List channels matching a string (regexp) and quit"],
		
	   ["GUI:separator",   "Hamlib Controls:"],
	   ["P|port=s",        "Port to use (/dev/ttyS0)"],
	   ["M|model=s",         "Rig model (122)"],
	   ["S|speed=s",       "serial speed"],

	   ["GUI:separator",   "Setup options:"],
	   ["c|config",        "Config file to load"],
	   ["test",            "Test retunning speed of the receiver"],
	   ["test-freqs=s",    "Set the starting and various frequencies to jump to"],
	   ["v|verbose",       "Verbose mode"],

	   ["GUI:separator",   "Output Controls:"],
	   ["g|gnuplot=s",     "gnuplot data file"],
	   ["G|graphic=s",     "png data output"],
	   ["w",                "create an output window"],

	   ["nogui", "Don't show the gui"],
	  ) || die "bad arguments";

$| = 1;

#
# read the config file
#
read_config($opts{'c'} || $defopts{'c'});

Die("No rig model set; -M, -P and -S are mandatory") if (!exists($opts{'M'}));
Die("No rig serial port set; -M, -P and -S are mandatory") if (!exists($opts{'P'}));
Die("No rig serial rate; -M, -P and -S are mandatory") if (!exists($opts{'S'}));

# initialize the rig connection
Hamlib::rig_set_debug(1);
my $rig = new Hamlib::Rig($opts{'M'});
$rig->set_conf("rig_pathname", $opts{'P'});
$rig->set_conf("serial_speed",$opts{'S'});
$rig->open();
my $freq = $rig->get_freq();
my %data;
our $maxscancount = $opts{'scanmax'};

if ($opts{'test'}) {
    my $startfreq = 14.250;
    my @jumpvalues = qw(.001 .00001 .25);
    if ($opts{'test-freqs'}) {
	($startfreq, @jumpvalues) = split(/,/,$opts{'test-freqs'});
    }

    $rig->set_freq($startfreq * 1000000);
    sleep(1);

    my $max = 0;
    foreach my $delta (@jumpvalues) {
	my $value = 0;
	Verbose("setting to $startfreq\n");
	$rig->set_freq($startfreq * 1000000);
	$value = $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);
	Verbose("  immediate value: $value\n");
	usleep(.5 * 1000000);
	$value = $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);
	Verbose("  initial value: $value\n");
	$value = 0;
	Verbose("  setting to " . ($startfreq+$delta) . "\n");
	$rig->set_freq(($startfreq+$delta) * 1000000);
	my $count = 0;
	$value = -54;
	my $prevvalue;
	my $startedat = [gettimeofday];
	while ($value < -32) {  # kinda arbitrary
	    $prevvalue = $value;
	    $value = $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);
	    $count++;
	    usleep(.00005 * 1000000);
	}
	Verbose("  final value: $value (was $value; $count polls to change)\n");
	my $diff = tv_interval($startedat, [gettimeofday]);
	Verbose("  $delta change => $diff\n");
	$max = ($max > $diff) ? $max : $diff;
    }
    printf("\nRecommended -u setting: %f (which is 5 * $max)\n",$max*5);

    exit;
}

sub find_group_members {
    my @names = @_;
    my @ret;
    foreach my $name (@names) {
	if (exists($groups{uc($name)})) {
	    push @ret, find_group_members(@{$groups{uc($name)}})
	} else {
	    die "ack: no such channel: $name" if (!exists($config{$name}));
	    push @ret, $name;
	}
    }
    my %done;
    my @out;
    map { push @out, $_ if (!$done{$_});
	  $done{$_} = 1;
      } @ret;
    return @out;
}

our $currentgroupconfig;
sub load_group {
    my ($group) = @_;
    $currentgroupconfig = $group;
    @toscan = find_group_members(split(/,\s*/,uc($group)));
    Verbose("scan list: ", join(", ", @toscan),"\n");
}

#
# Load previous state
#
if ($opts{'load'}) {
    my $var = do "$opts{'load'}";
    %data = %$var;

#
# just set a particular frequency
#
} elsif ($opts{'set'}) {
    Verbose("Setting channel to $opts{'set'}\n");
    set_channel($opts{'set'});
    exit;

#
# just set a particular frequency
#
} elsif ($opts{'list'}) {
    $opts{'list'} = uc($opts{'list'});
    foreach my $channel (keys(%config)) {
	if ($channel =~ /$opts{'list'}/o) {
	    printf("%-20s %-3d  %s\n", $channel, $config{$channel}{'priority'},
		   $config{$channel}{'frequency'});
	}
    }
    exit;

#
# Scanning
#
} elsif ($opts{'scan'}) {
    @toscan = keys(%config);
    if ($opts{'scan'} ne 1) {
	load_group($opts{'scan'});
    }


    if ($opts{'w'}) {
	# open a window and start the scanner with it's own
	# background timer.
	
	require CQ::App;
	$CQ::App::type = 'scan';
	my $app = CQ::App->new(); # create
	$app->MainLoop();	# go
    } else {
	while (1) {
	    usleep(next_scan() * 1000000);
	}
    }
} else {
    for (my $freq = $opts{'s'}; $freq <= $opts{'e'}; $freq += $opts{'i'}) {
	$rig->set_freq($freq * 1000000);

	my $value;
	my $count = 0;
	while ($count < $opts{'samples'}) {
	    usleep($opts{'u'} * 1000000);
	    $value += $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);
	    $count++;
	}
	$data{$freq} = $value / $count;
    }
}

    if ($opts{'state'}) {
	open(O,">$opts{'state'}");
	print O Dumper(\%data);
	close(O);
    }

my @keys = sort keys(%data);
my @sortedbylevel = sort { $data{$b} <=> $data{$a} } @keys;
my $leveltobeat = $opts{'l'} || $data{$sortedbylevel[int($#sortedbylevel/2)]};

my $ingood = 0;
my $startgood = 0;
my $level;
Verbose("finding signals above:  $leveltobeat\n");
for (my $i = 0; $i < $#keys; $i++) {
    if (!$ingood && $data{$keys[$i]} > $leveltobeat) {
	# starting a signal
	$ingood = 1;
	Verbose("good starting at: $keys[$i]\n");
	$startgood = $keys[$i];
	$level = $data{$keys[$i]};
    } elsif ($ingood && $data{$keys[$i]} <= $leveltobeat) {
	# falling out
	$ingood = 0;
	Verbose("	    end: $keys[$i]\n");
	printf ("	   diff: %-4.4f\n", ($keys[$i] - $startgood));
        Verbose("   level: $level\n");
	Verbose("	 center: " . ($startgood + ($keys[$i] - $startgood)/2) . "\n");
    } elsif ($ingood) {
	$level = $data{$keys[$i]} if ($level < $data{$keys[$i]});
    }
}

if ($opts{'g'}) {
    open(G, ">$opts{'g'}");
    foreach my $key (@keys) {
	print G "$key  $data{$key}\n";
    }
    close(G);
}

if ($opts{'G'}) {
    my $im = create_freq_graph();
    open(G,">$opts{'G'}");
    binmode G;
    print G $im->png;
}

if ($opts{'w'}) {
    my $im = create_freq_graph();
    open(G,">/tmp/cq.png");
    binmode G;
    print G $im->png;
    close(G);
}

sub create_freq_graph {
    Die("You need to install the GD module before you can create graphs")
      if (! eval { require GD; });
    import GD;
    my $height = 100;
    my $im = new GD::Image($#keys,$height);
    my $fill = $im->colorAllocate(0,0,255);
    my $white = $im->colorAllocate(255,255,255);
    $im->fill(1,1,$white);

    my $count = 0;
    foreach my $key (@keys) {
	$im->rectangle($count, $height - ($data{$key} + 50), $count+1, $height, $fill);
	$count++;
    }
    return $im;
}

#
# Wx widget support
#
if ($opts{'w'}) {
    require CQ::App;
    my $app = CQ::App->new();	# create
    $app->MainLoop();		# go
}

sub get_level {
    return $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);
}

sub get_frequency {
    return $rig->get_freq() / 1000000;
}

sub set_channel {
    my ($channel, $justbasics, $label) = @_;
    die "no channel to switch to" if (!$channel);
    $channel = uc($channel);
    $currentstart = time();

    # mark the current channel as done history-wise
    close_history($currentchannel, $currentstart+1);

    # set the frequency
    my $freq = $config{$channel}{'currentfrequency'}
      || $config{$channel}{'frequency'};
    $rig->set_freq($freq * 1000000);
    $rig->set_mode($modes{$config{$channel}{'mode'}},0)
      if ($config{$channel}{'mode'});
    $currentchannel = $channel;

    # mark the new channel as beginning
    start_history($channel, $currentstart, $label);

    return if ($justbasics);

    $rig->set_ctcss_tone($config{$channel}{'tone'} * 10)
      if ($config{$channel}{'tone'});
    $rig->set_dcs_code($config{$channel}{'dcs'} * 10)
      if ($config{$channel}{'dcs'});
}

sub start_history {
    my ($channel, $time, $label) = @_;
    $time = time() if (!$time);
    $label = "" if (!$label);
    while ($#{$config{$channel}{'history'}} > -1 &&
	   $time - $config{$channel}{'history'}[0][1] > $opts{'keephistory'}) {
	shift @{$config{$channel}{'history'}};
    }
    push @{$config{$channel}{'history'}}, [$label, $time];
}

sub set_history_label {
    my ($channel, $label) = @_;
    $config{$channel}{'history'}[$#{$config{$channel}{'history'}}][0]
      = $label;
}

sub get_history_label {
    my ($channel) = shift;
    return if (!$channel);
    return $config{$channel}{'history'}[$#{$config{$channel}{'history'}}][0];
}

sub close_history {
    my ($channel, $time, $label) = shift;
    return if (!$channel);
    $time = time() if (!$time);

    # ugh: fill in the closing history time if and only if
    # the last history slot wasn't closed yet

    if ($#{$config{$channel}{'history'}
	   [$#{$config{$channel}{'history'}}]} == 1) {
	$config{$channel}{'history'}[$#{$config{$channel}{'history'}}][2]
	  = $time;
	if ($label) {
	    $config{$channel}{'history'}[$#{$config{$channel}{'history'}}][0]
	      = $label;
	}
    }
}


my $scancount = 0;
# bogus search key: scan_next
sub next_scan {
    my $time = time();
    my $next;
    my $currentlevel;

    die "no scan list available!" if ($#toscan == -1);

    if ($locked) {
	Verbose("  Locked...\n");
	return 1;
    }

    # determine the number of channels to scan at once
    # this really only affects the time returned at the end
    if ($scancount == 0) {
	$scancount = $opts{'C'};
	$scancount = $config{$currentchannel}{'scancount'}
	  if ($currentchannel && $config{$currentchannel}{'scancount'} > 0);
	# (allow for a master-set maximum)
	$scancount = $maxscancount if ($scancount > $maxscancount);
    }
    $scancount--;

    #
    # see if the current channel is still active (or else forget it
    #
    while ($currentchannel || $#pastchannels > -1) {

	if ($currentchannel eq '') {
	    $currentchannel = shift @pastchannels;
	    next if (!$currentchannel);
	    set_channel($currentchannel);  # XXX: need a min setting; not full
	    Verbose("falling back to $currentchannel\n");
	}

	# see if current is active
	$currentlevel = $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);

	# completely fall off the current channel if it's been
	# inactive for longer than the specified hangtime.
	if ($currentlevel < $opts{'l'} &&
	    $currentlivetime +
	    ($config{$currentchannel}{'hangtime'} || $opts{'hangtime'})
	    < $time) {
	    Verbose(sprintf("current: -- $currentchannel ended: %ds --\n",
			    $currentlivetime-$currentstart));

	    close_history($currentchannel, $time);

	    $currentchannel = '';
	    $currentlivetime = 0;
	    next;
	}

	if ($currentlevel > $opts{'l'}) {
	    $currentlivetime = $time;
	}
	# it's still "active" and thus we should stop looking here
	last;
    }

    #
    # find the next channel to look for.
    #
    my $label = get_history_label($currentchannel);
    foreach my $key (@toscan) {

	my $checkevery = $config{$key}{'checkevery'} || $opts{'time'};

	next if ($key eq $currentchannel);

	# if disabled, forget it
	next if ($config{$key}{'enabled'} eq 'false');

	# if the current channel is active and is a higher
	# priority, then skip this one.
	next if ($currentchannel &&
		 $config{$key}{'priority'} >
		 $config{$currentchannel}{'priority'});

	# if we've checked it recently enough, don't do again
	# (assuming we're listening to something, otherwise
	# check anyway even though it's early).
	next if ($currentchannel &&
		 ($time - $config{$key}{'lastchecked'} < $checkevery));

	# ok, then we should definitely search it.  But we
	# need to pick the best one, so see if we have another
	# one that needs to be checked first.
	# XXX: optionally use priorities instead of time for sorting?
	if (!$next) {
	    $next = $key;
	} elsif ($config{$key}{'lastchecked'} + $checkevery
		 < $config{$next}{'lastchecked'}
		 + ($config{$next}{'checkevery'} || $opts{'time'})) {
	    $next = $key;
	}
    }

    #
    # now that we've found one to check, see if we should switch to it
    #
    if ($next) {
	my $frequency;

	# normal channel or frequency range?
	if ($config{$next}{'endfrequency'}) {
	    # we're scanning a whole range; take the next step forward
	    do {
		if ($config{$next}{'currentfrequency'}) {
		    $frequency = $config{$next}{'currentfrequency'} +
		      $config{$next}{'interval'} || $opts{'i'};
		    if ($frequency > $config{$next}{'endfrequency'}) {
			$frequency = $config{$next}{'startfrequency'};
		    }
		} else {
		    $frequency = $config{$next}{'startfrequency'};
		}
		$config{$next}{'currentfrequency'} = $frequency;
	    } until (!skip_it($frequency));
	} else {
	    # just a single channel
	    $frequency = $config{$next}{'frequency'};
	}

	# what to check next?

	start_history($next);
	# change the frequency and potentially the mode
	$rig->set_freq($frequency * 1000000);
	$rig->set_mode($modes{$config{$next}{'mode'}},0)
	  if ($config{$next}{'mode'});

	# sleep the scan time length
	usleep($opts{'u'} * 1000000);

	# test for the new level
	my $newchan = $rig->get_level_i($Hamlib::RIG_LEVEL_STRENGTH);;

	Verbose(sprintf("  checked %2d %-12s (pri=%-5d at %-8.8s every %2ds):  %d\n", $scancount, $next, $config{$next}{'priority'}, $frequency, $config{$next}{'checkevery'}, $newchan));

	# XXX: minimum timeout for falling back somehow?

	# if the new level is better, we'll use it (because the
	# priority is already higher).
	if ($newchan > $opts{'l'}) {
	    # use the current frequency of the new channel
	    # remember this one as the new "best"
	    close_history($currentchannel, $time);
	    push @pastchannels, $currentchannel if ($currentchannel);
	    $currentchannel = $next;
	    $currentstart = $time;
	    Verbose("current: $currentchannel\n");
	    set_history_label($currentchannel, 'found');
	} else {
	    close_history($next, $time+1);
	}


	# remember the last time we checked this new channel
	$config{$next}{'lastchecked'} = time();
    }

    if ($currentchannel ne '') {
	# if we're doing more than one at once, return a small
	# fraction since we've already waited the minimum on the
	# current channel.
	return $opts{'u'}/100 if ($next && $scancount > 0);

	$scancount = 0;
	set_channel($currentchannel, 0, $label) if ($label);
	return 1; # XXX: make configurable
    } else {
	return .0001; # XXX: make configurable (1ms minimum for Wx; 10ms pause)
    }
}

sub skip_it {
    my ($freq) = @_;
    foreach my $skip (@skips) {
	if ($freq >= $skip->[0] && $freq <= $skip->[1]) {
	    return 1;
	}
    }
    return 0;
}

sub read_config {
    my ($file, $defaults) = @_;
    my $fh = new IO::File;
    $fh->open("<$file");
    my $line = 0;
    my %defaults = ();
    %defaults = %$defaults if ($defaults);

    while (<$fh>) {
	chomp();
	$line++;
	next if (/^\s*#/);
	next if (/^\s*$/);

	if (/^\s*default:\s*(.*)/) {
	    foreach my $data (split(/,\s*/,$1)) {
		my ($left, $right) = split(/\s*=\s*/, $data);
		$defaults{$left} = $right;
	    }
	} elsif (/^\s*include\s+(.*)/) {
	    read_config($1, \%defaults);
	} elsif (/^\s*libs\s+(.*)/) {
	    unshift @INC, $1;
	} elsif (/^\s*skip:\s+(.*)/) {
	    my $skip = $1;
	    if ($skip =~ /-/) {
		push @skips, split(/\s*-\s*/, $skip);
	    } else {
		push @skips, [$skip,$skip];
	    }
	} elsif (/^\s*option\s+(\S*)\s+(.*)/) {
	    my ($name, $value) = ($1, $2);
	    $opts{$name} = $value if (!exists($opts{$name}));
	} elsif (/^\s*group\s+([^:]+):\s*(.*)/) {
	    my $groupname = $1;
	    my $groups = $2;
	    push @{$groups{uc($groupname)}}, split(/,\s*/, uc($groups));
	} elsif (/^\s*(\S+)\s*:\s*([^,]*),*(.*)/) {
	    my $parts;
	    my $name = uc($1);
	    if (exists($config{$name})) {
		$parts = $config{$name};
	    } else {
		%$parts = %defaults;
		$config{$name} = $parts;
	    }
	    $parts->{'frequency'} = $2 if ($2);
	    foreach my $data (split(/,\s*/,$3)) {
		my ($left, $right) = split(/\s*=\s*/, $data);
		$parts->{$left} = $right;
	    }

	    if ($parts->{'frequency'} =~ /(.*)-(.*)/) {
		$parts->{'startfrequency'} = $1;
		$parts->{'endfrequency'} = $2;
	    }
	} else {
	    Error("Error in config file on line $line");
	}
    }
    $fh->close();

    # copy in the defaults
    foreach my $def (keys(%defopts)) {
	$opts{$def} = $defopts{$def} if (!exists($opts{$def}));
    }
}

sub Error {
    print STDERR @_,"\n";
}

sub Die {
    Error(@_);
    exit 1;
}

sub Verbose {
    print STDERR @_ if ($opts{'v'});
}

#######################################################################
# Getopt::GUI::Long portability wrapper
#
sub LocalGetOptions {
    if (eval {require Getopt::GUI::Long;}) {
	import Getopt::GUI::Long;
	# optional configure call
	Getopt::GUI::Long::Configure(qw(display_help no_ignore_case
					capture_output));
	return GetOptions(@_);
    }
    require Getopt::Long;
    import Getopt::Long;
    # optional configure call
    Getopt::Long::Configure(qw(auto_help no_ignore_case));
    my $ret = GetOptions(LocalOptionsMap(@_));
    if ($opts{'h'}) {
	print STDERR "You need to install the perl Getopt::GUI::Long perl module to get help output;\n";
	exit 1;
    }
    return $ret;
}

sub LocalOptionsMap {
    my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH')
			    ? (1, 1, $_[0]) : (0, 2));
    for (my $i = $st; $i <= $#_; $i += $cb) {
	if ($_[$i]) {
	    next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
	    push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
	    push @opts, $_[$i+1] if ($cb == 2);
	}
    }
    push @opts,"h|help";
    return @opts;
}

=cut

=head1 NAME

cq - scan a set of channels or frequencies

=head1 SYNOPSIS

cq operates in two modes: scanning channels for traffic or scanning
for generating a histogram.

=head1 USAGE

  cq -scan repeatername1,repeatername2,simplexname1,...

  Install Getopt::GUI::Long for extend command line help.

=head1 COMMAND LINE OPTIONS

The command line options can also be specified in the ~/.cqrc file.
See below for details on doing this.

=over

=item -scan SCANITEM1, SCANITEM2, ...

Scans the various channels or groups for traffic based on the channel
settings.

=item -w

Brings up a cute little window (requires the Wx perl module) which
allows you to force-lock to certain channels, switch scanning groups,
enable/disable channels, etc.

=item --test

Tests how long it takes before your rig can report a non-zero signal
level.  Generally after changing frequecies the rig will need a
fraction of a second to tune to the new channel.  You should run this
test (defaults to 14.XXX frequencies for testing) to see how long it
believes the rig needs.  Then use the resulting calculated value with
the -u flag.

=item -u

How long to wait after switching to a channel before believing the
report s-meter value.  .1 is probably a safe setting for most rigs.
Faster means faster scanning or less pauses.

=item -P PORT

The tty port to use.

=item -M MODEL

The Hamlib rig model to use (see the hamlib documents for details on
what number to use with this flag).

=item -S SPEED

The serial port speed to use.  Make sure the rig is set to the same speed.

=item -c FILE

The config file to read.  By default this is $HOME/.cqrc

=item -v

Verbose mode: it prints details about it's current scanning activity.

=item --hangtime SECONDS

The amonut of time after locking to a new channel to leave the scanner
there even when the channel goes idle.  Defaults to 3 seconds (meaning
it'll wait 3 seconds before falling back to lower-priority channels).

=item -l LEVEL

The minimum S-meter level to use when looking for active channels.
Defaults to -12.

=back

=head1 CONFIG FILE

$HOME/.cqrc can contain the following definition lines:

=head2 INCLUDING OTHER FILES

To include another file, use:

  include /path/to/file

=head2 CHANNEL LINES

Lines that define channels look like:

  repeatername: 147.000,checkevery=5,priority=10

Which specifies the frequency to check, how often to check it and it's
priority level.

Ranges are also allowed (though not as tested or documented).

  20mSSB: 14.150-14.350,interval=.01

=head3 Channel and Range Tokens

=over

=item mode

The mode to switch the rig to (FM, AM, USB, LSB, CW, ...).  If not set
cq will not change it, which is probably bad unless all the channels
are of the same type.

=item checkevery

How often (in seconds) to check the channel for traffic.

=item priority

Lowered numbered priorities will be considered more important; higher
number priorities will not be searched at all until the lower number
priorities are completely silent (longer than their hangtime).

(useful tip: set your local faviorite radio station (e.g. NPR or rock
station) to priority 10000 and everything else lower than that; it'll
ensure you're always listening to at least something while it's
scanning around your other defined channels).

=item interval

Used for scanning...  Must. Write. Docs.

=item tone

The CTCS tone that the rig should be set to when the channel is locked
onto (e.g. "123.0").

=item dcs

The DCS code that the rig should be set to when the channel is locked
onto (e.g. "123.0").

=item enabled

Generally this isn't needed, but if set to I<false> it won't be used.

=item hangtime

The amount of time the channel should stay locked to after it goes
silent.  See the --hangtime option too (per-channel settings override
the command line).

=item scancount

The number of stations away from the current station that will be
searched in a row before returning to the current station.  A setting
of 1 will minimize the pause while switching away; higher settings
will result in a higher pause while it searches for more channels but
will have a better chance of catching higher priority traffic.

The default can be set with the -C option.

=back

=head2 SKIPPING

For frequency scanning, you can skip certain known channels that may
have constant tones on them in you local area.  EG:

  6mscan: 50.100-50.300,interval=.01
  skip: 50.130

will allow you to scan from 50.100 to 50.300 but skips 50.130 when
it's searching.

Or to skip a range (somewhere with a long tone that will drown
everything out):

  skip: 50.130-50.140

=head2 OPTION LINES

You can also add command line options to the .cqrc file.  They must be
the short-anme options (cq -h output) if they exist.  As an example:

  option M 122

Sets the default '-m' flag option to 122 (ie, set thes hamlib rig
model to 122 which is a Yaesu 857).

=head2 LIBRARY PATH

If you have some perl modules in a non-standard directory:

  libs /path/to/the/dir/theyre/in 

=head1 EXAMPLE CONFIG FILE

  #
  # Default command line flags
  #
  option	    S 38400
  option      u .8

  #
  # default channel options
  #
  default: mode=FM
  default: priority=100
  default: checkevery=5
  default: hangtime=5

  #
  # Repeaters
  # (repeater1 is a favorite and has a lower priority)
  repeater1   : 147.000,tone=100,priority=50,checkevery=1
  repeater2   : 147.195,tone=123

  group repeaters: repeater1, repeater2

  #
  # Simplex Channels
  #
  2MFMCall    : 146.52
  Geocache    : 147.555
  SSBFun      : 144.200,mode=USB

  group simplex: 2MFMCall, Geocache, SSBFun

  #
  # A few FRS Frequencies
  #
  default: priority=2000
  default: checkevery=10
  FRS1  :462.625
  FRS2  :462.875
  FRS3  :462.125

  group FRS:  FRS1, FRS2, FRS3

  #
  # FM Radio Stations
  #
  # my favorite radio channel
  KXJZ: 90.9,priority=10000

  #
  # Combining a few groups
  #

  # combine the simplex and repeater groups
  group daily: simplex, repeaters

  # add in a radio station to always fall back on
  group alwayson: daily, KXJZ

With the above, you can scan various groups of channels easily:

  Scans just the simplex and repeaters
  # cq -scan daily

  Adds in a radio station to fall back to so it's always playing something:
  # cq -scan alwayson

  Maybe we want to check the FRS channels before falling back to the
  radio station (remember the FRS had a lower priority so it'll be
  picked in preferenc to the KXJZ station).
  # cq -scan alwayson,FRS



=head1 AUTHOR

Wes Hardaker AKA WS6Z < wes ATAT ws6z DOT com >

=head1 License

Copyright 2008 Wes Hardaker
All rights reserved

Licensed under GPLv2

=cut

# not needed but attempted by File::Temp
#perl2exe_exclude VMS::Stdio

#I understand needing this one:
# (which doesn't work because it needs VMS/Stdio.pm???)
#perl2exe_include File::Temp
#perl2exe_include Getopt::GUI::Long

#Probably don't need these technically?
#perl2exe_include bytes
#perl2exe_include attributes
#perl2exe_bundle "/usr/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi/auto/Hamlib/Hamlib.so"
#perl2exe_bundle "/usr/lib/libhamlib.so.2"
#perl2exe_bundle "/usr/lib/hamlib-yaesu.so"
#perl2exe_bundle "/usr/lib/hamlib-dummy.so"
#perl2exe_bundle "/usr/lib/libusb-0.1.so.4"
#perl2exe_bundle "/usr/lib/hamlib-alinco.so"
#perl2exe_bundle "/usr/lib/hamlib-aor.so"
#perl2exe_bundle "/usr/lib/hamlib-drake.so"
#perl2exe_bundle "/usr/lib/hamlib-dummy.so"
#perl2exe_bundle "/usr/lib/hamlib-easycomm.so"
#perl2exe_bundle "/usr/lib/hamlib-flexradio.so"
#perl2exe_bundle "/usr/lib/hamlib-fodtrack.so"
#perl2exe_bundle "/usr/lib/hamlib-icom.so"
#perl2exe_bundle "/usr/lib/hamlib-jrc.so"
#perl2exe_bundle "/usr/lib/hamlib-kachina.so"
#perl2exe_bundle "/usr/lib/hamlib-kenwood.so"
#perl2exe_bundle "/usr/lib/hamlib-kit.so"
#perl2exe_bundle "/usr/lib/hamlib-lowe.so"
#perl2exe_bundle "/usr/lib/hamlib-microtune.so"
#perl2exe_bundle "/usr/lib/hamlib-pcr.so"
#perl2exe_bundle "/usr/lib/hamlib-racal.so"
#perl2exe_bundle "/usr/lib/hamlib-rft.so"
#perl2exe_bundle "/usr/lib/hamlib-rotorez.so"
#perl2exe_bundle "/usr/lib/hamlib-rpcrig.so"
#perl2exe_bundle "/usr/lib/hamlib-rpcrot.so"
#perl2exe_bundle "/usr/lib/hamlib-sartek.so"
#perl2exe_bundle "/usr/lib/hamlib-skanti.so"
#perl2exe_bundle "/usr/lib/hamlib-tapr.so"
#perl2exe_bundle "/usr/lib/hamlib-tentec.so"
#perl2exe_bundle "/usr/lib/hamlib-tuner.so"
#perl2exe_bundle "/usr/lib/hamlib-uniden.so"
#perl2exe_bundle "/usr/lib/hamlib-winradio.so"
#perl2exe_bundle "/usr/lib/hamlib-wj.so"
#perl2exe_bundle "/usr/lib/hamlib-yaesu.so"
