Wednesday, October 23, 2013

Pushing TED 5000 data as snapshot graphs with Chart::Clicker

I wanted to make my TED 5000-C power monitoring accessible both for internal clients that didn't have the horsepower to run Footprints, and also publish the data externally for remote clients.

 I put together a simple Perl script which uses the 1-minute-resolution historical data from the TED-5000 to build a simple graph using Chart::Clicker, like this:


#!  /usr/bin/perl
 #
# Ted-5000 Chart script - Copyright 2013 by Kevin Kadow
# This program is free software: you can redistribute it and/or modify it under the terms of the 
# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, 
# or (at your option) any later version.   Use this at your own risk, no warranty is expressed nor implied. YMMV
#
#
use  Time::Local;
use LWP::Simple;
use Chart::Clicker;
use Chart::Clicker::Data::Series;
use Chart::Clicker::Data::DataSet;
use Chart::Clicker::Renderer::Area;
use Chart::Clicker::Axis::DateTime;
use List::Util qw(min max);

# Variables you need to edit
#
my($ip,$mtuid)=("TED5000","0"); # Replace TED5000 with IP address or hostname of your TED

my $chart_png="/var/www/htdocs/plotwatt/recent.png";

my $time_zone="America/New_York";

# Variables seldom edited
my $modulus=2;
my $records=180;
my $volts_minumum=110;
$debug=0; 
# Based on the TED API
my $url="http://$ip/history/minutehistory.xml?MTU=$mtuid&COUNT=$records&INDEX=1";

my $content = get $url;
die "Couldn't get TED 5000 history from $url" unless defined $content;

#
# Parse the history, turn it into a list of $meter,KW,timestamp,...

my($firstdate,$lastdate);
foreach $line (split(/[\n\r]+/,$content)) {
        $count{'lines'}++;
        chomp $line;

        my($kw,$mtu,$date,$power,$timestamp);
        if($line=~m!(\d+)(.+)(\d+)\d+(\d+)!) {
                $count{'parsed records'}++;

                $mtu=$1; $date=$2;$power=$3; $volts=$4;
                $firstdate=$date;
                $lastdate=$date unless($lastdate);
                $kw=$power/1000;
                $volts=$volts/10;


                unless($date=~m!(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+)!) {
                        warn "Bad date '$date'\n";
                        next;
                        }

                # Convert date string like '10/22/2013 20:07:23' to UTC EPOCH
                $timestamp= timelocal($6,$5,$4,$2,$1-1,$3);
                if(abs($timestamp - time)> 186400 ) {
                        $count{'bad timestamp'}++;
                        warn "Bad timestamp $timestamp is not close to current";
                        next;
                        }

                # Store the raw data in associative arrays
                $wattage{$timestamp}=$power;
                $voltage{$timestamp}=$volts;
                # And arrays for charts
                unshift(@watts,$kw);
                unshift(@volts,$volts);
                unshift(@timestamps,$timestamp);
                next;
                }
        next if ($line=~m!! || !length($line));
        last if ($line=~m!!);
        warn "\nBad line '$line'\n\n";
        $count{'could not parse'}++;
        }
#
# Print a bunch of handy stats, if debugging is on.
#
if($debug) {
        warn "Sending $postdata\n" if($debug >1);

        warn "Results of parsing TED data:\n";
        foreach $key (sort keys %count) {
                warn "\t$key\t$count{$key}\n";
                }
        }

#
# Build the chart
#
my $points = scalar @watts;
print "Will chart $points from Watts @watts\n" if($debug);
if($points < 2) {
        warn "Trying to chart $points datapoints\n";
        return(undef);
        }

#
# Build a chart object
#
my $chart = Chart::Clicker->new;
my $dstamper=Chart::Clicker::Axis::DateTime->new(position => 'bottom', orientation     => 'horizontal', time_zone => $time_zone);

my $context = $chart->get_context('default');
$context->range_axis->format('%.3f');
$context->domain_axis->ticks( 6 );
$context->domain_axis($dstamper);

my $watts_line = Chart::Clicker::Data::Series->new({
        keys   => \@timestamps,
        values => \@watts,
        name   => 'Kilowatts',
});
my $wcontext= Chart::Clicker::Context->new( name => 'Watts' );
$chart->add_to_contexts($wcontext);
my $wdataset = Chart::Clicker::Data::DataSet->new( series => [ $watts_line]);
$wdataset->context('Watts');
$wcontext->range_axis->format('%.2f');
$wcontext->range_axis->range->min(int(min(@watts)));
#$wcontext->domain_axis->hidden(1);
#$wcontext->domain_axis->ticks( $points );
$wcontext->domain_axis($dstamper);

$chart->add_to_datasets( $wdataset );


my $volts_line = Chart::Clicker::Data::Series->new({
        keys   => \@timestamps,
        values => \@volts,
        name   => 'Volts',
});

my $vcontext= Chart::Clicker::Context->new( name => 'Volts' );

$chart->add_to_contexts($vcontext);
my $vdataset = Chart::Clicker::Data::DataSet->new( series => [ $volts_line]);

$vdataset->context('Volts');
$vcontext->range_axis->format('%.0f');
$vcontext->range_axis->range->min($volts_minumum);
#$vcontext->domain_axis->ticks( 6 );
$vcontext->domain_axis($dstamper);

$chart->add_to_datasets( $vdataset );

$lastdate=~s/:00$//; $firstdate=~s/:00$//;
$chart->title->text("TED $firstdate through $lastdate");
my $renderer = Chart::Clicker::Renderer::Area->new( opacity => .75, );
$chart->set_renderer($renderer,'Watts');
$chart->write_output( $chart_png );

exit(0);