Simple event graphing using Linux tools and gnuplot

For those of you who can do simple programming under Unix/Linux, you can get quick-and-dirty gnuplot graphs of your Hubitat device event data from the real-time exported JSON records sent out by the Maker API App.

You don't need to install node-red, javascript, or any databases, and you don't need to poll your hub. All you need is a machine on your local network with shell access that is running a web server, that lets you run CGI scripts, and lets you write to a file.

The Maker API App lets you set a URL to POST device events to. That means that for every device you select under Select Devices, every event those devices produce will be sent in JSON format to that URL in real time.

Create that URL to be a CGI script on a machine on your local net that is running a web server. Make the CGI script simply append the JSON records to an output file on the machine. No polling is needed, assuming the devices generate their own events periodically.

Since the JSON produced by Maker API is missing a time/date stamp, I had my CGI script add one at the beginning using sed:

#!/bin/sh -u
# My URL for this script is: http://192.168.1.1/~idallen/makerapi.cgi

echo "Content-Type: text/plain"
echo ""

# Change the output file below to be where you store all your event data.
# Use $(date +%s --date='now UTC') if you want UTC time stamps (see `gnuplot` below).
exec sed -e "s/\"name\"/\"date\":\"$(date +%s)\",&/" -e "s/\$/\n/" \
    >>/idallen/tmp/makerapi.json 2>&1

As soon as you create the above script, make it executable, and put the URL for it into the Maker API App configuration, you'll start seeing JSON event data being appended to the file in real time for the devices you selected in the Maker API App configuration.

If you don't see anything, make sure your device is configured to generate its own events fairly frequently, and check your web server logs and web error logs for script and file permission errors. Make sure your web server is configured to permit CGI scripts in the directory you are using.

If you already have a database or existing graph infrastructure (e.g. Grafana), you could make the CGI script add the event data to the database directly, instead of putting it in a file. Perl has database tie-in functions; you could also use your favourite CGI language to do the same. I won't cover that here.

Here is a sample line from my generated .json output file (note the leading date field added by my CGI script above):

{"content":{"date":"1653841789","name":"power","value":"426.974","displayName":"Aeotec Energy Meter Gen5","deviceId":"1","descriptionText":null,"unit":"W","type":null,"data":null}}

Make a note to roll and empty this log file occasionally, or else you'll fill up your file system with device events.

With the JSON data being logged into a file in real time, all you need to do is pull the values you want to graph out of the file.

You could use jq to convert the JSON records to CSV for input to your favourite CSV graphing program:

$ jq -r '."content" | [.date, .name, .value] | @csv' \
    < /idallen/tmp/makerapi.json >data.csv

I wanted to combine three JSON power entries onto one line for input to gnuplot, so I wrote a small Perl program to read the JSON records and output one line with the three power readings on the one line:

#!/usr/bin/perl -w
# Read the makerapi.json file and consolidate multiple power
# reports onto one line based on leading epoch seconds timestamp.

use strict;
use warnings;
use JSON;

my @powers = qw( power power-Clamp-1 power-Clamp-2 );
my %triple = ();
my $prevdate = 0;

while(<>) {
    next unless /^[{]/;
    my $objr = decode_json($_);
    die unless $objr;
    my $cont = ${$objr}{'content'};
    next unless defined($cont);
    die unless ref($cont);
    my $dn = ${$cont}{'displayName'};
    # only look at desired device
    next unless $dn eq 'Aeotec Energy Meter Gen5';
    my $name = ${$cont}{'name'};
    # only look at desired power fields
    next if ! grep(/^$name$/,@powers);
    my $date = ${$cont}{'date'};
    # if more than a second apart, start over
    if ( ($date-$prevdate) > 1 ) {
        %triple = ();
    }
    my $value = ${$cont}{'value'};
    $triple{$name} = $value;
    my $size = keys(%triple);
    # When we have all three keys, we can output them on one line:
    if ( $size == 3 ) {
        print "$date $triple{'power'} $triple{'power-Clamp-1'} $triple{'power-Clamp-2'}\n";
        %triple = ();
    }
    $prevdate = $date;
}

The above Perl script produces one line with three power levels on it:

$ ./i.pl /idallen/tmp/makerapi.json | head -n 4
1653841789 426.974 381.851 45.123
1653841794 427.62 382.428 45.192
1653841799 428.802 383.64 45.162
1653841804 430.294 385.181 45.113

That output can be put into a file and fed into gnuplot:

$ ./i.pl /idallen/tmp/makerapi.json >mydata
$ gnuplot -persist -e "
    set xdata time ; set timefmt '%s' ; set format x \"%Y
%m/%d
%H:%M\"  ;
    set grid ;
    set title 'Power consumption' ;
    plot 'mydata' using 1:2 t 'power',
         'mydata' using 1:3 t 'clamp1', 
         'mydata' using 1:4 t 'clamp2' ; 
 "

The X-axis graph times will appear in UTC unless you apply a time correction for your local time zone. One way to fix this is to adjust UTC to EDT by subtracting four hours in the gnuplot script itself:

    [...]
    plot 'mydata' using (column(1)-4*60*60):2 t 'power',
         'mydata' using (column(1)-4*60*60):3 t 'clamp1', 
         'mydata' using (column(1)-4*60*60):4 t 'clamp2' ; 

A better way to fix the UTC problem for gnuplot is to write UTC times in the CGI script. In the CGI script, change the command substitution $(date +%s) to $(date +%s --date='now UTC') and then you don't need to do the subtraction in the gnuplot script, nor do you need to keep track of Daylight Savings Time. This time adjustment may or may not be good for other plotting programs.

With appropriate scripting or gnuplot plot limits, you can limit the data plots to date ranges:

$ start=$( date '+%s' --date="May 29 3pm UTC" )
$ end=$( date '+%s' --date="May 29 5pm UTC" )
$ gnuplot -persist -e "
[...]
    plot [$start:$end] 'mydata' using (column(1)-4*60*60):2 t 'power',
[...]

Suggestion for Maker API: add a time/date stamp to the JSON records.

2 Likes

Download the Hubitat app