PINE64::MAX7219

I have had another cpan module published! This one is a driver for a MAX7219 8-digit LED display on the PINE64 single board computer. The video above is a demo of some of the module’s capabilities. It is implemented as bit-banged SPI using the perl programming language.

A very easy to use display, it only uses 3 GPIO pins, and could be used to output the value of sensors, or simple menus for projects.

PINE64::GPIO module published on CPAN

my first perl module published on CPAN

One of my personal goals this year was to publish some of my work that uses perl to experiment with single board computers on CPAN (the Comprehensive Perl Archive Network). I have used perl to make windows applications, database applications, web applications, text processing utilities, inventions….. you name it. However, it is not widely used for single board computers like the raspberry pi. There are a lot of reasons for this. Perl is no longer the cool programming language say, like Python is. Programming in perl is almost an anachronism and dates you. It is relatively slow. Even so, perl is incredibly versatile and powerful and has a devoted following of some of the most brilliant people. Perl isn’t going anywhere.

I have been a consumer of CPAN for nearly 10 years. CPAN is a code repository where programmers share their code so you don’t have to re-invent the wheel in your programs if someone else has already figured out how to implement what you are trying to do.

Steve Bertrand and others have published a ton of useful modules for the raspberry pi family of SBC’s on CPAN. Dude even published a book on indiegogo on pi projects using perl. I am not near as smart as Mr. Bertrand, so I wanted to stay away from publishing my pi modules on CPAN. The idea is to do something that hasn’t been done before. No one like in the whole world seems to be using perl on the Pine64 board (if you google, I am virtually the only person doing it), so I decided to post my modules for the PineA64+.

To publish on CPAN, you have to have a PAUSE account (Perl Authors Upload Server). You have to be ‘in the club’. You basically have to apply for membership stating what you intend to offer. I also gave them the address of this site so they could see some of my work. I didn’t hear anything back for nearly six weeks. I assumed they looked at my projects and declined to grant me access. I finally did get an account and proceeded to package my modules to their requirements and uploaded my first module: a script that controls the GPIO pins on the PineA64 board’s Pi-2 bus. I consider it a tremendous honor to have been accepted.

I have a technology degree with some graduate work in computer science. I work in telecom, however, not as a programmer. Programming, though, is my secret weapon. Paired with the knowledge of electronics hardware and fabrication, I can create just about anything I can think up. It is a thrill to finally get published on CPAN. I will have many more modules for the PineA64 to follow for various sensors, analog to digital converters, displays, etc.

arduino based hvac lead-lag controller

arduino lead-lag hvac control
Installed lead-lag controller
arduino lead-lag hvac control
arduino lead-lag hvac control
arduino hvac lead-lag controller
lead-lag controller using arduino nano

My office is in a telecommunications facility full of routers, radios, fiber-optic equipment etc. Reliable environmental controls are essential as this equipment will fail in extreme heat. I have two 20,000 BTU AC units in my office that run on 250VAC. I created this lead-lag controller to turn on the lead AC unit based on the temperature setting, and if the lead unit has run for six minutes without cooling to the setting, turns on the lag unit as well until the temperature setting is reached.

arduino lead-lag hvac controller
arduino lead-lag hvac controller

I have the programming down and the device built and installed. Initial tests work as designed. I am using some 10A 250VAC power relays. With the unit running on high fan speed and the compressor going the AC units draw about 5 amps; well within the rage of the relays.

the lead / lag controller turns on/off the 250VAC outlets of the air conditioner units

Here is the code ->

// include the library code:
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "RTClib.h"
RTC_DS3231 rtc;

// Sensor input pin
#define DATA_PIN 2
// How many bits to use for temperature values: 9, 10, 11 or 12
#define SENSOR_RESOLUTION 9
// Index of sensors connected to data pin, default: 0
#define SENSOR_INDEX 0

OneWire oneWire(DATA_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress sensorDeviceAddress;

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 10, d5 = 9, d6 = 8, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
int sample_num = 0;

//thermostat setting; init to 72
int setting = 70; 

//heat or cool mode
char mode = 'C';

//relay pins
const int ac1Pin = 5;
const int ac2Pin = 4;
//const int heatPin = 6;

//control buttons
const int modePin = 3;
const int decrPin = 6;
const int incrPin = 0;

//AC ON/OFF flags
int ac1_f = 0;
int ac2_f = 0;
int heat_f = 0;

//on timers
int ac1_timer = 0;
int ac2_timer = 0;

//RTC stuff
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  Serial.begin(9600);

  //AC1 relay
  pinMode(ac1Pin, OUTPUT); 
  pinMode(ac2Pin, OUTPUT);
  //pinMode(heatPin, OUTPUT);

  //set AC1 off
  digitalWrite(ac1Pin, LOW);
  digitalWrite(ac2Pin, LOW);
  //digitalWrite(heatPin, LOW);    

  //control buttons
  pinMode(modePin, INPUT);
  pinMode(decrPin, INPUT);
  pinMode(incrPin, INPUT);

  sensors.begin();
  sensors.getAddress(sensorDeviceAddress, 0);
  sensors.setResolution(sensorDeviceAddress, SENSOR_RESOLUTION);  

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }  
}

  //array to hold 12 readings; 5-per min
  float t_readings[12];

  //sum of readings
  float sum = 0.0;

  //avg of readings
  float average = 0.0;

void loop() {
  
  // set the cursor to (0,0):
  lcd.setCursor(0, 0);

  //reset sum and avg on each iteration
  sum = 0;
  average = 0;

  //12 sample loop; takes 1 min
  for(int x=0;x<12; x++){
    lcd.setCursor(0, 0);
    sensors.requestTemperatures();
    // Measurement may take up to 750ms

    float temperatureInCelsius = sensors.getTempCByIndex(SENSOR_INDEX);
    float temperatureInFahrenheit = sensors.getTempFByIndex(SENSOR_INDEX);
  
    Serial.print("Temperature: ");
    Serial.print(temperatureInCelsius, 1);
    Serial.print(" Celsius, ");
    Serial.print(temperatureInFahrenheit, 1);
    Serial.print(" x: ");
    Serial.print( x );
    Serial.println(" Fahrenheit"); 

    //push reading into array
    t_readings[x] = temperatureInFahrenheit;
  
    lcd.print(temperatureInFahrenheit);
    lcd.print("F");
    //lcd.setCursor(0, 1);
    //lcd.print(sample_num);
    //sample_num++;

    if(digitalRead(modePin) == LOW){
      Serial.println("MODE BUTTON PRESSED! ENTERING SETUP....");
      setTemp();
    }//end if mode pressed

    //print setting to lcd row 2
    lcd.setCursor(0, 1);
    lcd.print("Setting: ");
    lcd.print(setting);    

    //print mode
    lcd.setCursor(15,0);
    lcd.print(mode);

    delay(5000);  //sleep 5 sec
  }//end for loop 12 sample

  //average samples
  for(int i=0; i<12; i++){
    sum = sum + t_readings[i];
  }//end for avg
    Serial.print("sum: ");
    Serial.println(sum);
    average = sum / 12;
    Serial.print("avg: ");
    Serial.println(average);

    //AC relay control
    if(average > setting && mode == 'C'){
      digitalWrite(ac1Pin, HIGH);

      //flag logic
      if(ac1_f == 0){ ac1_f = 1;}

      //timer logic
      ac1_timer += 60;    //60 because of the 12 samples/min cycle
      Serial.print("AC UNIT 1 ON ");
      Serial.print(ac1_timer);
      Serial.print(" seconds ");

      if(ac1_timer > 60){//turn on ac2 after 30 minutes
        if(ac2_f == 0){ 
          ac2_f = 1; 
          digitalWrite(ac2Pin, HIGH);
        }
        else{
          //increment ac2_timer
          ac2_timer += 60;
        }
        Serial.print("AC UNIT 2 ON ");
        Serial.print(ac2_timer);
        Serial.print(" seconds ");
      }//end if turn on AC2

      //poll RTC for time
      DateTime now = rtc.now();
   
      Serial.print(now.year(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.day(), DEC);
      Serial.print(" "); 
      Serial.print(" (");
      Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
      Serial.print(") ");
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.println(now.second(), DEC);
    }//end if
    if(average < setting && mode == 'C'){
      //turn AC1 OFF, reset timer & flags
      digitalWrite(ac1Pin, LOW);
      ac1_f = 0;
      ac1_timer = 0;
      Serial.print("AC UNIT 1 OFF ");

      //if on, turn off AC2
      if(ac2_f == 1){
          digitalWrite(ac2Pin, LOW);
          ac2_f = 0;
          ac2_timer = 0;
          Serial.print("AC UNIT 2 OFF ");
      }//end if
      
      //poll RTC for time
      DateTime now = rtc.now();
      
      Serial.print(now.year(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.day(), DEC);
      Serial.print(" "); 
      Serial.print(" (");
      Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
      Serial.print(") ");
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.println(now.second(), DEC);      
    }//end if
    
    delay(2000); 
}

void setTemp(){
  int su_flag = 1;
  int mode_flag = 1;
  Serial.println("SETUP MODE ->");
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set temp: ");
  lcd.print(setting);
  delay(1000);
  //lcd.clear();
  while(su_flag == 1){
    if(digitalRead(decrPin) == LOW){
      setting--;
      lcd.setCursor(10, 0);
      lcd.print(setting);
    }//end if exit setup
    if(digitalRead(incrPin) == LOW){
      setting++;
      lcd.setCursor(10, 0);
      lcd.print(setting);
    }//end increment temp
    if(digitalRead(modePin) == LOW){
      //exit setup
      su_flag = 0;
    }//end exit setup mode
    delay(250);
  }//end while
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set mode: ");
  lcd.print(mode);
  delay(1500);
  while(mode_flag == 1){
    if(digitalRead(decrPin) == LOW){
      mode = 'C';
      lcd.setCursor(10, 0);
      lcd.print(mode);
    }//end if
    if(digitalRead(incrPin) == LOW){
      mode = 'H';
      lcd.setCursor(10, 0);
      lcd.print(mode);
    }//end if heat mode
    if(digitalRead(modePin) == LOW){
      mode_flag = 0;
    }//end if exit mode set
    delay(250);
  }//end while set mode
  lcd.clear();
}//end setTemp function

Beethoven Piano Sonata No. 4 E-flat Major (Grand Sonata)

First Movement

2020 is the 250th anniversary of Beethoven’s birth. Mrinda and I were supposed to attend an all Beethoven concert celebrating this anniversary at the Germantown performing arts center, with the main event being a performance of Beethoven’s first piano concerto and Valery Kuleshov as soloist. It was cancelled due to COVID-19.

However, in honor of this important date, I wanted to learn some Beethoven sonatas that I had never worked on or even heard. I went to the IMSLP and listened to all 32 of them and found a few that I was interested in.

No. 4 in E-flat major is really fun to play. Andras Schiff has a brilliant lecture on this piece here. I found it interesting that this is the longest sonata next to the Hammerklavier. I was totally unfamiliar with this sonata, so it was a great surprise.

Toccata in E minor BWV 914

my performance of Toccata in E minor BWV 914

The Baroque toccata is one of my favorite musical forms in the way it brings out what the instrument and the performer are capable of. BWV 914 is a masculine, muscular early work of Bach’s that gives me the same healthy release of anger that an old Metallica song does. It has that youthful vigor and creativity of an emerging artist that has not yet refined his style. It definitely bears resemblance to Buxtehude’s organ toccatas, in that it contains so many movements and musical ideas in one piece. Later on, Bach settled on the two movement prelude/fuge, or the toccata/fuge.

My performance here is riddled with mistakes and timing issues. I suffer from stage fright, even when I am alone playing in front of a camera. Playing Bach makes me even more nervous. Oh well, I’m just an amateur with limited time to practice.

perl interface to 18B20 1-wire temperature sensor

raspberry pi zero interfacing with 18B20 1-wire temperature sensor

I am pretty impressed with the responsiveness and accuracy of the 1-wire 18B20 temperature sensor. This is the first 1-wire sensor I have ever used. To get it going on the pi zero, I had to enable the 1-wire protocol via raspi-config, and edit the /boot/config.txt file to tell it what pin to use. Like gpio, 1-wire has a file system interface, which makes perl a great tool to interface to 1-wire devices.

Interestingly, each 1-wire device has a unique serial number, so it is possible to have literally thousands of 1-wire devices on a single bus; certainly far more than i2c allows. Here is the command line output of a manual reading from the 18B20:

file system interface on pi zero for a 1-wire 18B20 temperature sensor
file system interface on pi zero for a 1-wire 18B20 temperature sensor

The part of the output that says t=26500 is the temperature reading in celsius i.e. 26.500 or 79.7F.

Here is my perl code for interfacing with the 18B20 ->

#!/usr/bin/perl -w
use strict;

=pod
reads a 1wire 18B20
include this in your script, 
returns fahrenheit
=cut

#my $temp = read_18b20();
#print "temp: $temp\n";

sub read_18b20{
	`ls /sys/bus/w1/devices > 1w.txt`;
	open OW, "1w.txt" or die $!;
	my $temp_sensor = '';
	while(<OW>){
		if($_ =~ /(28-............)/){
			#print "serial number:  $1\n";
			$temp_sensor = $1;	
		}#end if
	}#end while
	close OW;

	my ($tempC, $tempF) = (0,0);
	`cat /sys/bus/w1/devices/$temp_sensor/w1_slave > 1w_reading.txt`;	
	open RD, "1w_reading.txt" or die $!;
	while(<RD>){
		if($_ =~ /t=(\d\d)(\d\d\d)/){
			$tempC = $1.".".$2;
			#print "celsius: $tempC\n";	
		}#end if
	}#end while
	close RD;
	$tempF = $tempC * (1.8) + 32;
	#print "farenheit: $tempF\n";
	
	return $tempF; 
}#end read_18b20

1;

Here is sample output from a script that samples the sensor every 2 seconds. I breathed on the 18B20 to demonstrate the responsiveness.

samples from a 1-wire 18B20 temperature sensor using perl interface
samples from a 1-wire 18B20 temperature sensor using perl interface

arduino servo example analysis

futaba s3003 arduino servo example script
futaba s3003 arduino servo example script

I admit I know little about pulse width modulation, and have limited experience with robotics or servos. I’ve had a futaba s3003 servo in my toolbox for some time, and done nothing with it. On a lazy day when I had some down time, I decided to get it out and do some experimentation.

I’ve been doing some labor intensive projects at work lately, and while hard work is very rewarding, it takes a lot of physical and mental energy and crowds out innovation. Just out of boredom, I burned the most basic servo script example provided by the arduino IDE into a nano clone I recently bought, and analyzed it with pulseview to better understand pwm.

close up of PWM controlling a futaba s3003 servo
close up of PWM controlling a futaba s3003 servo

Above is a trace of the output of the default arduino ide servo script with me tuning a 10K pot. In this example script, the position of the servo corresponds to the position of the potentiometer. Just at a glance, you can see pulses bunched up at different intervals. What I empirically observed is that when the servo was at position 1, the pulse width was about 0.55ms in duration at the standard 50Hz required by the servo. This pulse was constant, and the servo was stationary. If the pulse width changes at all, it changes position proportionally.

The futaba s3003 has 180 degrees of rotation. At position 3 (180 degrees) the constant pulse width is about 2.4msec. as you can see in the graphic below:

futaba s3003 servo at 180 degrees of rotation

This simple analysis has demystified pwm somewhat for me. Next, I hope to experiment with some pwm on the raspberry pi using perl.

using an arduino as an ADC for a raspberry pi

reading a battery discharge with an arduino
discharging a 6V lead acid cell with two 50 ohm 10 watt resistors in parallel, reading the voltage with an arduino nano clone, and logging the data with a pi zero

Most everyone knows that unlike an arduino, the pi lacks an analog to digital converter. I normally use an MCP3004. These work ok for me most of the time, but I get a lot of readings that are outliers, and obviously not correct. I haven’t noticed this as much on the analog inputs on an arduino.

In this experiment (for an upcoming project) I essentially let an arduino nano clone read the voltage of a 6V lead acid battery that I was discharging with some low-value, high wattage resistors. The arduino read the voltage and printed it on it’s serial line at 9600 baud. The serial lines on the arduino were connected to the pi’s serial lines, and the pi recorded the readings by logging a screen session. First, you have to disable getty on the serial line, and then set up screen to log the session.
sudo systemctl stop serial-getty@ttyAMA0.service
sudo screen -L /dev/ttyAMA0 9600

I forgot to mention, that I used a 10K potentiometer as a voltage divider to step down the 6V so the arduino could read it.

6V battery discharging over 9 hour period under moderate load

I got a pretty good dataset with very little jitter and hardly any outliers. I think a pi and an ardino make a powerful combination. You can effectively extend all the arduino’s gpio’s, i2c, spi, etc. to the pi. You also have all the muscle and storage of the pi at your disposal for your projects.

rssi vs. relative humidity

receive signal strength indication vs. relative humidity data logger
receive signal strength indication vs. relative humidity data logger

I finally got around to deploying my rssi / temperature / relative humidity data logger and recorded nearly 6,000 samples of each metric over a five day period. This radio system operates in the ISM band, and is susceptible to propagation issues due to atmospheric conditions, especially humidity. The logic board provides a terminal that outputs a DC voltage that represents the rssi in dBm. My data logger uses a raspberry pi zero w, and reads the rssi voltage with an MCP3004 analog to digital converter using a bit-banged driver I wrote in perl. Each reading is timestamped using time derived from a DS3231 i2c real-time clock chip.

I use a HTU21 temperature / relative humidity sensor break out board from adafruit. This chip is MUCH MORE STABLE & RELIABLE than the DHT22 I was originally using.

temperature vs. relative humidity data capture
temperature vs. relative humidity data capture

For starters, here is the temperature in fahrenheit (red) vs. the relative humidity (green). Predictably, when the temperature rises, the relative humidity goes down and vice versa.

rssi level vs. relative humidity at 900MHz
rssi level vs. relative humidity at 900MHz

Now here is the relative humidity vs. the rssi. This radio is about 20 miles away from the transmitter over flat terrain with an output of 37dBm (5 watts), and has an average rssi of about -90dBm. You can see from the above graph that at about sample 4,000 when the relative humidity takes a noticeable dip that the rssi has a corresponding, albeit small, increase: precisely what we would expect to see.

Nothing surprising here. It just proves the effectiveness of my data logger device. It would be useful in situations where an RF path is going up and down due to atmospheric conditions or other obstructions, and real-time data would be useful in diagnosing problems and coming up with solutions like a higher gain antenna or increasing height.

network scanner with perl + Tk

perl Tk ip scanner application
perl Tk ip scanner application

I enhanced my command line ip scanner by adding a Tk gui and adding a few more features.  Above, I am scanning a range of contiguous addresses on my home network.

It can also accept a list of hosts from a csv file.

list of hosts in csv format
list of hosts in csv format

Here are the results from the scan of the above list.

perl Tk ip scanner results using csv file input
perl Tk ip scanner results using csv file input

I am forced against my will to use Windows 10 on my PC at work.  I also lack admin privileges, so I cannot install a free ip scanner application.  I do, however, have ActivePerl installed on my box, and it has tons of modules so I can write my own utilities.

I have several classes of ip devices (routers, switches, battery chargers, etc.) that I want to monitor and have created individual csv files for each.  This simple script is highly effective at discovering devices on a subnet, or to give a quick glance at what devices on a list are online.

 
     1	#!/usr/bin/perl -w
     2	use strict;
     3	use Tkx;
     4	use Tkx::Scrolled;
     5	use Net::Ping;
     6	use Time::HiRes qw(gettimeofday);
       
     7	#main window
     8	my $mw = Tkx::widget->new(".");
     9	$mw->g_wm_title("nnmap network scanner");
    10	$mw->g_wm_geometry("800x600");
       
    11	#global vars
    12	my $ip_range = '';
    13	my $tdelay = 250; 
       
    14	#create ping object
    15	my $p = Net::Ping->new('icmp');
    16	#hi res time
    17	$p->hires();
       
    18	#a single ip address
    19	my $host;
       
    20	#up / down devices
    21	my @online_devices; 
    22	my @offline_devices; 
       
       
    23	#main content frame
    24	my $mfrm = $mw->new_ttk__frame(-padding => "5 10");
    25	$mfrm->g_grid(-column => 1, -row => 0, -sticky => "news");
    26	$mfrm->new_ttk__label(-text => "ip range")->g_grid(-column => 1, -row => 1, -sticky => "nw", -padx => 3, -pady => 1);
    27	$mfrm->new_ttk__entry(-textvariable => \$ip_range, -width => 24)->g_grid(-column => 1, -row => 2, -sticky => "nw", -padx => 3, -pady => 1);
    28	$mfrm->new_ttk__label(-text => "delay msec")->g_grid(-column => 1, -row => 3, -sticky => "nw", -padx => 3, -pady => 1);
    29	$mfrm->new_ttk__spinbox(-from => 1, -to => 900, -width => 5, -textvariable => \$tdelay)->g_grid(-column => 1, -row => 4, -sticky => "nw", -padx => 1, -pady => 2);
    30	$mfrm->new_ttk__button(-text => "scan", -command => sub {scan_net($ip_range);})->g_grid(-column => 1, -row => 5, -sticky => "nw", -padx => 1, -pady => 2);
       
    31	my $ta = $mfrm->new_tkx_Scrolled(
    32	    'text',
    33	    -scrollbars => "e",
    34	    -width => 70,
    35	    -height => 33,
    36	    -state => "normal"
    37	);
    38	$ta->g_grid(-column => 2, -row => 1, -sticky => "e", -padx => 5, -pady => 5, -rowspan => 50);
       
    39	#colors
    40	$ta->tag_configure("success", -foreground=>"white", -background=>"green");
    41	$ta->tag_configure("failure", -foreground=>"white", -background=>"red");
    42	#fonts
    43	$ta->tag_configure("lgtxt", -font =>"r18", -relief=>"raised", -underline=>1);
       
    44	sub scan_net{
    45		my $func_name = (caller(0))[3];
    46		print "Called $func_name on line ". __LINE__."\n";
    47		
    48		print "ip range: $ip_range\n";
       
    49		#parse ip range
    50		#only for class c or smaller subnets
    51		#?'s in regex account for a single ip address to scan
    52		$ip_range =~ /(\d+\.\d+\.\d+)\.(\d+)-?(\d+)?/;
    53		my $network = $1;
    54		my $start_ip = $2;
    55		my $end_ip = $3;
    56		
    57		print "end_ip: $end_ip\tstart ip: $start_ip\n";
       
    58		#account for just one ip to scan
    59		if($end_ip eq ""){
    60			$end_ip = $start_ip; 
    61		}#end if
       
    62		#convert time delay
    63		my $ping_delay = $tdelay * 0.001;
       
    64		#empty text area
    65		$ta->delete("1.0", "end");
       
    66		#get t0 for benchmark
    67		my $t0 = gettimeofday();
       
    68		#line counter
    69		my $line_n = 1; 
       
    70		for(my $i=$start_ip;$i<=$end_ip;$i++) { 71 $host = "$network.$i"; 72 print "scanning host: $host\n"; 73 #list context, returns duration 74 my ($ret, $dur, $ip) = $p->ping($host, $ping_delay);
    75			
    76			#format time
    77			$dur = sprintf("%.6f", $dur); 	
       
    78			#results
    79			if($ret){
    80				#print WHITE ON_GREEN "$host is up  latency: $dur seconds", RESET;
    81				#form response string
    82				my $response = "$host is up  $dur seconds";
    83				my $res_len = length($response); 
    84				if($res_len < 70){#fill up ta rows with color
    85					my $n_sp = 70 - $res_len; 
    86					for(my $x=0;$x<$n_sp;$x++){ 87 $response = $response." "; 88 }#end for add sp 89 }#end if 90 $response = $response."\n"; 91 #print "res_len: $res_len\n"; 92 #print "\n"; 93 $ta->insert("$line_n.0", $response);
    94				$ta->tag_add("success", "$line_n.0", "$line_n.0 lineend");
    95				push @online_devices, $host;
    96			}#end if
    97			else{
    98				my $response = "$host down";
    99				my $res_len = length($response); 
   100				if($res_len < 70){
   101					my $n_sp = 70 - $res_len; 
   102					for(my $x=0;$x<$n_sp;$x++){ 103 $response = $response." "; 104 }#end for add sp 105 }#end if 106 $response = $response."\n"; 107 #print "res_len: $res_len\n"; 108 #print WHITE ON_RED "$host down", RESET; 109 #print "\n"; 110 $ta->insert("$line_n.0", $response);
   111				$ta->tag_add("failure", "$line_n.0", "$line_n.0 lineend");
   112				push @offline_devices, $host;
   113			}#end else		
       
   114			#increment line number
   115			$line_n++;
   116		
   117		}#end for
       
   118		#results
   119		#benchmarking results
   120		my $t1 = gettimeofday();
   121		my $elapsed = $t1 - $t0; 
   122		$elapsed = sprintf("%6f", $elapsed);
   123		print "\ntime elapsed: $elapsed....\n";
   124		my $up_sz = @online_devices;
   125		my $down_sz = @offline_devices;
   126		print "$up_sz devices online\n$down_sz devices offline\n";
       
   127		#form results
   128		my $results = "Results\ntime elapsed: $elapsed\n\n$up_sz devices online\n$down_sz devices offline\n";
   129		
   130		#insert results
   131		$ta->insert("$line_n.0", $results);
   132		$ta->tag_add("lgtxt", "$line_n.0", "$line_n.0 lineend");
   133	}#end scan_net
       
   134	sub openCSV{
   135		my $func_name = (caller(0))[3];
   136		print "Called $func_name on line ". __LINE__."\n";
       
   137		my $fn = Tkx::tk___getOpenFile();
   138		print "FILE: $fn\n";
       
   139		#open csv file
   140		open HF, $fn, or die $!;
       
   141		#empty text area
   142		$ta->delete("1.0", "end");
       
   143		#ping delay
   144		my $ping_delay = $tdelay*0.001;
       
   145		#get t0 for benchmark
   146		my $t0 = gettimeofday();
       
   147		#line number counter
   148		my $line_n = 1;
       
   149		while(){
   150			my $ipdevice = $_;
   151			my @ipdev = split(',', $ipdevice); 
   152			my $description = $ipdev[1]; 
   153			$host = $ipdev[2]; 		
   154			$host =~ s/\s+//;
   155			$description =~ s/\n//;
   156			
   157			#list context, returns duration
   158			my ($ret, $dur, $ip) = $p->ping($host, $ping_delay);
       
   159			print "scanning host: $host\n";
   160			
   161			#format time
   162			$dur = sprintf("%.6f", $dur); 	
   163			
   164			my $host_desc = $host."\t\t".$description;
   165			
   166			#results
   167			if($ret){
   168				#print WHITE ON_GREEN "$host $description is up  latency: $dur seconds", RESET;
   169				#print "\n";
   170				my $response = "$host $description is up  $dur sec";
   171				my $res_len = length($response); 
   172				if($res_len < 70){#fill up ta rows with color
   173					my $n_sp = 70 - $res_len; 
   174					for(my $x=0;$x<$n_sp;$x++){ 175 $response = $response." "; 176 }#end for add sp 177 }#end if 178 $response = $response."\n"; 179 $ta->insert("$line_n.0", $response);
   180				$ta->tag_add("success", "$line_n.0", "$line_n.0 lineend");
   181				push @online_devices, $host_desc;
   182			}#end if
   183			else{
   184				#print WHITE ON_RED "$host $description down", RESET;
   185				#print "\n";
   186				push @offline_devices, $host_desc;
   187				my $response = "$host $description down";
   188				my $res_len = length($response); 
   189				if($res_len < 70){#fill up ta rows with color
   190					my $n_sp = 70 - $res_len; 
   191					for(my $x=0;$x<$n_sp;$x++){ 192 $response = $response." "; 193 }#end for add sp 194 }#end if 195 $response = $response."\n"; 196 $ta->insert("$line_n.0", $response);
   197				$ta->tag_add("failure", "$line_n.0", "$line_n.0 lineend");
   198			}#end else
       
   199			$line_n++;
   200		}#end while
   201		
   202		close HF; 
       
   203		#results
   204		#benchmarking results
   205		my $t1 = gettimeofday();
   206		my $elapsed = $t1 - $t0; 
   207		$elapsed = sprintf("%6f", $elapsed);
   208		print "\ntime elapsed: $elapsed....\n";
   209		my $up_sz = @online_devices;
   210		my $down_sz = @offline_devices;
   211		print "$up_sz devices online\n$down_sz devices offline\n";
       
   212		#form results
   213		my $results = "Results\ntime elapsed: $elapsed\n\n$up_sz devices online\n$down_sz devices offline\n";
   214		
   215		#insert results
   216		$ta->insert("$line_n.0", $results);
   217		$ta->tag_add("lgtxt", "$line_n.0", "$line_n.0 lineend");
   218	}#end openCSV
       
   219	#menu
   220	Tkx::option_add("*tearOff", 0);
   221	$mw->configure(-menu => mk_menu($mw));
   222	sub mk_menu{
   223		my $mw = shift;
   224		my $menu = $mw->new_menu();
   225		my $file = $menu->new_menu( -tearoff => 0);
   226		$menu->add_cascade(
   227			-label => "File", 
   228			-underline => 0, 
   229			-menu => $file
   230		);
   231		$file->add_command(
   232			-label => "Open CSV File", 
   233			-underline => 0, 
   234			-command => sub {openCSV();}
   235		);
   236	          $file->add_command(
   237	              -label   => "Exit",
   238	              -underline => 1,
   239	              -command => [\&Tkx::destroy, $mw],
   240	          );
   241		return $menu;
   242	}#end mk_menu
       
   243	Tkx::MainLoop();

This is one of those utilities of mine that will probably continue to grow in complexity.