Files
wlan-lanforge-scripts/attenuator_series.pl
2020-09-09 11:08:04 -07:00

358 lines
12 KiB
Perl
Executable File

#!/usr/bin/perl
##
## Reads a CSV of attenuator settings and plays them back
## Remember that 300 is deci-dB; eg 300: sets a channel to 30.0 dB
##
use strict;
use warnings;
use diagnostics;
use Carp;
$SIG{__DIE__} = sub{Carp::confess(@_)};
use Getopt::Long;
use lib '/home/lanforge/scripts';
use Net::Telnet;
use Time::HiRes qw(usleep);
use LANforge::Utils;
use LANforge::csv qw();
$| = 1;
our $usage = qq($0: replay a csv file of attenuator values
--mgr|m LANforge manager host
--file|f CSV file
--delay|d Override of %delay variable, milliseconds between applying rows
--loop|l Repeat indefinitely
--channel|c Override of channels variable, eg: 1.2.3.1,2.3.4.3
--minimum|min|i Set minimum attenuation value (not lower than zero)
--maximum|max|x Set maximum attenuation value (not higher than 955)
--dry_run|dryrun|dry|n Do not apply attenuation, just parse file, ignore nap times
Example that works on localhost manager:
$0 --file values.csv
Example that overrides delay to 1600, overrides channels and runs once:
$0 --mgr 192.168.101.1 --file values.csv --delay 1600 --channel 1.1.3.1,1.1.3.2,1.1.3.3
Example that overrides delay to 600ms, loops forever, and overrides min and max attenuation
$0 -m 192.168.101.1 -f values.csv -d 600 -l -min 10 -max 900
File Format:
# < comment lines are ignored
# 60 milliseconds between rows
delay,60
# Directives: DELAY,delay and naptime are equivalent
# Sets minimum and maximum attenuation for all channels
min,10
max,900
# Directives: MINIMUM,MAXIMUM,MIN,MAX,minimum,min,maximum and max are allowed
# The next line defines column B as attenuator channel 1.1.13.1
# and column C as attenuator channel 2.1.25.1. Remember that
# attennuator channels are values (shelf).(resource).(serialno).(channel)
# and channels are presently values {1, 2, 3, 4}.
channels,1.1.13.1,2.1.25.1
# Directives: CHANNELS,channels are equivalent
# Attenuation values are in deci-dBm, resolution of 5ddB:
# The next line sets 1.1.13.1 to 36.5dB, 2.1.25.1 to 30.0dB:
attenuate,365,300
# Directives: ATTENUATE,attenuate, "", and _ are equivalent.
# The next line leaves 1.1.13.1 alone, sets 2.1.25.1 to 31.0dB,
# _ is an abbreviation for attenuate
_,NA,+10
# The next line leaves 1.1.13.1 alone, sets 2.1.25.1 to 30.5dB,
# Blank first column is an abbreviation for attenuate
,NA,-5
# Only some basic CSV formulas are interpretable, and only operate
# on the previous values of the attenuator; the next line sets
# sets 1.1.13.1 to 36.0dB, sets 2.1.25.1 to 31.0dB
,=B6-5,=C6+5
# does nothing for a period
_,_,NA,,
# does nothing for 35ms
sleep,35
# Directives: SLEEP,sleep, and nap are equivalent
);
our $csvfile = undef;
our $delay = -1;
our $delay_override = -1;
our $do_loop = 0;
our @channels = (); # in order list of channels
our %last_atten = (); # a map of last-known values
our $channel_override= undef;
our $quiet = "yes";
our $line = 0; # line number
our $lfmgr_host = "localhost";
our $lfmgr_port = 4001;
our $dryrun = 0;
our $min_atten = 0;
our $max_atten = 995;
GetOptions (
'manager|mgr|m=s' => \$::lfmgr_host,
'mgr_port|port|p=i' => \$::lfmgr_port,
'file|f=s' => \$::csvfile,
'delay|d=i' => \$::delay_override,
'loop|l' => \$::do_loop,
'channels|c' => \$::channel_override,
'quiet|q=s' => \$::quiet,
'dry_run|dry|n' => \$::dryrun,
'minimum|min|mn|i=i' => \$::min_atten,
'maximum|max|mx|x=i' => \$::max_atten,
) || die("$::usage");
die("Please specify a manager address;\n$::usage")
if (!defined $::lfmgr_host || "$::lfmgr_host" eq "");
die("Please specify a csv file;\n$::usage")
if (!defined $::csvfile || "$::csvfile" eq "");
die("Unable to find csv file: $::csvfile")
unless(-f $::csvfile );
our $cfile=new LANforge::csv();
$::cfile->readFile($::csvfile);
if ($::cfile->numRows < 1) {
die( "empty file, nothing to do");
}
if ($::quiet eq "1" ) {
$::quiet = "yes";
}
elsif ($::quiet eq "0" ) {
$::quiet = "no";
}
if (defined $::channel_override && "$::channel_override" != "") {
for my $c ( split(/,/, $::channel_override)) {
push(@::channels, $c);
$::last_atten{$c} = 0;
}
}
die("Minimum attenuation must be between [0-954]")
if ($::min_atten > 994 || $::min_atten < 0);
die("Maximum attenuation must be between [1-995]")
if ($::max_atten > 995 || $::max_atten < 1);
die("Minimum attenuation must be less than maximum attenuation")
if ($::max_atten <= $::min_atten);
sub lastAtten {
my $arg = shift;
die ("lastAtten: called without argument")
if (! defined $arg || "$arg" eq "");
if ($arg =~ /^\d+$/) {
if (!defined($::channels[$arg])) {
warn "Channels: ".join(', ', @::channels);
die ("no channel recorded at position $arg");
}
die ("no channel [$::channels[$arg]]")
if (!defined $::last_atten{$::channels[$arg]});
return $::last_atten{$::channels[$arg]};
}
elsif ($arg =~ /^\d+\.\d+\.\d+\.\d+$/) {
die ("no channel [$::channels[$arg]]")
if (!defined $::last_atten{$::channels[$arg]});
return $::last_atten{$arg};
}
die ("lastAtten: What is channel $arg?");
}
sub attenuate {
my $channel = shift;
my $value = shift;
die("attenuate: no line number")
if (!defined $::line || "$::line" eq "");
die("attenuate: $::line: no channel")
if (!defined $channel || "$channel" eq "");
return if (!defined $value || "$value" eq "");
return if (lc($value) =~ /^(na|_)$/);
return if (lc($value) =~ /^\s*[!;\#]/);
my ($shelf, $resource, $serno, $chan) = split(/\./, $channel);
#print "shelf:$shelf, r:$resource, ser:$serno, ch:$chan\n";
die( "[$::line] attenuate: shelf misconfigured:[$channel][$value]")
if ($shelf != 1);
die( "[$::line] attenuate: resource misconfigured:[$channel][$value]")
if ($resource < 1);
die( "[$::line] attenuate: serial number misconfigured:[$channel][$value]")
if ($serno < 1);
die( "[$::line] attenuate: channel misconfigured:[$channel][$value]")
if ($chan < 0 || $chan > 4);
my $prev_value = $::last_atten{$channel};
if ($value =~ /^[-+]/) {
die("[$::line] attenuate: no previous value set for $channel")
if (! defined $prev_value);
$value = $prev_value + (0+$value);
#warn "VALUE MATH[$value] ";
}
if ($value > $::max_atten) {
warn("[$::line] attenuate: value cannot be higher than $::max_atten")
unless($::quiet eq "yes");
$value = $::max_atten;
}
if ($value < $::min_atten) {
warn("[$::line] attenuate: value cannot be lower than $::min_atten")
unless($::quiet eq "yes");
$value = $::min_atten;
}
$::last_atten{$channel} = $value;
$::utils->doAsyncCmd("set_atten $shelf $resource $serno $chan $value")
unless (defined $::dryrun && $::dryrun);
print "$::line: set_atten $shelf.$resource.$serno.$chan $value\n"
if ($::quiet ne "yes" || $::dryrun);
}
##
## M A I N
##
# connect to manager
our $utils = new LANforge::Utils();
$utils->connect($lfmgr_host, $lfmgr_port);
if ($::quiet eq "yes") {
$::utils->cli_send_silent(1); # Do show input to CLI
$::utils->cli_rcv_silent(1); # Repress output from CLI ??
}
else {
$::utils->cli_send_silent(0); # Do show input to CLI
$::utils->cli_rcv_silent(0); # Repress output from CLI ??
}
if (defined $::delay_override && $::delay_override != -1 && $::delay_override < 1000) {
warn("$0: --delay is in milliseconds, values less than 1000 (1 second) might be meaningless");
sleep 2;
}
die ("$0: --delay of zero or less is not permitted.")
if (defined $::delay_override && $::delay_override != -1 && $::delay_override <= 0);
$::delay = $::delay_override if (defined $::delay_override && $::delay_override > 0);
my $loop_count = 0;
while ($loop_count == 0 || $::do_loop) {
$loop_count++;
for (my $rownum = 0; $rownum < $::cfile->numRows(); $rownum++) {
$::line = $rownum+1;
my $ra_row = $::cfile->getRow($rownum);
next if (@{$ra_row} == 0); # empty row
if (lc($ra_row->[0]) =~ /^(delay|naptime)$/) {
next if (defined $::delay_override && $::delay_override != -1);
$::delay = 0 + $ra_row->[1];
die ("$line: delay of zero or less is not permitted")
if ($::delay <= 0);
next;
}
if (lc($ra_row->[0]) =~ /^channels$/ && (!defined $::channel_override)) {
my @tempchannels = @$ra_row;
shift @tempchannels;
%::last_atten= ();
for my $c (@tempchannels) {
push(@::channels, $c);
$::last_atten{$c} = -1;
}
next;
}
if (lc($ra_row->[0]) =~ /^(sleep|nap)$/) {
if (!defined $ra_row->[1] || (0 + $ra_row->[1]) < 1) {
die("$line: sleep value needs to be 1ms or greater");
}
usleep($ra_row->[1] *1000) unless ($::dryrun);
next;
}
if (lc($ra_row->[0]) =~ /^(attenuate|_)$/ || $ra_row->[0] eq "") {
#print "\n";
my $col = 1;
foreach my $ch (@::channels) {
my $value = "NA";
my $data = $::cfile->getCell($col, $rownum, "na");
#print "DATA($col,$::line)[$data] ";
if (!defined $data || "$data" eq "" ) {
$col++;
next;
}
if (lc($data) =~ /^(na|_)$/ || $data =~ /^\s*\#.*$/) {
#warn ("skipping data[$data] at $col,$::line");
$col++;
next;
}
if ($data =~ /^\d+$/) {
$value = 0 + $data;
}
elsif ($data =~ /^=[B-Z]\d+[+-]\d+$/i) { # we have a formula
my ($acol,$arow,$delta) = $data =~ /^=([B-Z])(\d+)([+-]\d+)$/i;
$acol = ord(uc($acol)) - 65;
my $pval = $::cfile->getCell($acol, $arow-1, 0);
if (!defined $pval) {
$pval = lastAtten($col-1);# $::last_atten{$::channels[$col]};
warn("Failed to find valid references at cell[$col,$::line], using previous attenuation:".$pval);
}
if ( $pval !~ /^\d+$/) {
$value = lastAtten($col-1);# $::last_atten{$::channels[$col]};
die("Failed to find valid references at cell[$col,$::line]:".$value)
if ( ! defined $value);
#$value = $value + (0+$delta);
warn "Substituting [$value]: cell[$col,$::line] refers to cell[$acol,$arow] with non absolute value:$pval";
}
else {
$value = $pval + (0 + $delta);
}
#print "acol[$acol] arow[$arow] delta[$delta] pval[$pval] value[$value]\n";
}
elsif ($data =~ /^\@?[+]+\d+$/ ) { # add relative
my ($delta) = $data =~ /^\@?[+]+(\d+)$/;
my $pval = lastAtten($col-1); #$::last_atten{$::channels[$col]};
$value = $pval + (0 + $delta);
}
elsif ( $data =~ /^\@?[-]+\d+$/ ) { # subtract relative
my ($delta) = $data =~ /^\@?[-]+(\d+)$/;
my $pval = lastAtten($col-1); #$::last_atten{$::channels[$col]};
$value = $pval + (-1 * (0 + $delta));
}
else {
warn "Unknown directive[$data] ";
$col++;
next;
}
attenuate($ch, "$value");
$col++;
}
die("Step delay not set correctly[$::delay]")
if (!defined $::delay || "$::delay" eq "" || (0+$::delay) < 1);
usleep($::delay * 1000) unless ($::dryrun);
next
}
die("$::line: unknown directive[".$ra_row->[0]);
}
}
## eof