Pic n Mix IoT Data Collection


Pic n Mix IoT Data Collection

ThingSpeak is an excellent platform for quick and easy IoT Data Collection in the cloud.
As a test we have our office test Pic n Mix machine linked up and sending it’s vend stats once every 5 minutes to give an example of what can be done.

Iot Data Collection

Pic n Mix World machine at Adventure Island Southend, UK

IoT Data Collection

The machine is configured to run a perl script every 5 minutes to check the latest stats, if the stats don’t change then nothing happens apart from sending a heartbeat message. This allows us to see that the machine is still on and running. The data for this can be seen below.

Heartbeat Data

Updated every 5 minutes when the machine is running (ThingSpeak Channel)

Hopper Vend Counts

Updated when a vend happens (ThingSpeak Channel)

Hopper Vend Errors

Updated when a vend happens, this shows us which hoppers may be running low (ThingSpeak Channel)

Takings (not real money yet!)

Updated when a vend happens (ThingSpeak Channel)

Perl Script

The following is a quick and dirty hack that parses data from our vending machine stats application and is executed every 5 minutes as a cron job.

Note that our API_Keys have been removed

#!/usr/bin/perl
use Getopt::Std;
use Data::Dumper qw(Dumper);

$statsCmd="azucastats | grep RAW";


#valid value to show data is valid, returned as first field in RAW data
$validValue=43981;

#file used to store previous runs data so we only send data if needed
$datFile="/var/run/statsreport.dat";

#number of times to retry if invalid valid value is returned from the stats
$retryCount=3;

#api keys
$vendStatsApiKey="";
$hopperStatsApiKey="";
$hopperErrorApiKey="";
$heartbeatApiKey="";

#Vend Stats fields
$vsVendCountField="field1";
$vsVendsRemainingField="field2";
$vsEmergancyVendsField="field3";
$vsCashBoxField="field4";
$vsTotalCashField="field5";
$vsScaleFactorField="field6";

#Hopper stats field
$hs1Field="field1";
$hs2Field="field2";
$hs3Field="field3";
$hs4Field="field4";
$hs5Field="field5";
$hs6Field="field6";
$hs7Field="field7";
$hs8Field="field8";

#heartbeat fields
$hbField="field1";
$hbDeviceStateField="field2";
$hbErrField="field3";


#set to 1 by cmd option -n which will force a send of data to the server
$reportNow=0;

#base url
$baseUrl="https://api.thingspeak.com/update?api_key=";


sub printUsage()
{
	print "usage $0 -n\n";
	print "     -n force data send\n";
	exit;
}

sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };

#get the command lineoptions
my %options=();
getopts ("hn", \%options) or printUsage();

if (defined $options{h})
{
	printUsage();
}
if (defined $options{n})
{
	$reportNow=1;
	print "Forcing data send\n";
}
elsif ( !-e $datFile )
{
	$reportNow=1;
	print "Dat file does not exist, forcing data send\n";
	`touch $datFile` or die "Unable to create empty dat file $! ";
}

@statsFields=();

while ($retryCount-- != 0)
{
	$stats=`$statsCmd`;
	if ($? == -1 )
	{
		print "Failed to execute: $!\n"
	}
	elsif ($? & 127)
	{
		print "child died with signal %d, %s coredump\n",
				($? & 127),  ($? & 128) ? 'with' : 'without';
	}
	else 
	{
			printf "child exited with value %d\n", $? >> 8;
	}
	print "$stats";

	@statsFields = split(",", $stats);
	if ((@statsFields[0] eq "RAW " . $validValue))
	{
		last;
	}
	else
	{
		print "Warning, invalid raw value: " . @statsFields[0] . "\n";
	}
}

if ($retryCount < 0)
{
	die "Invalid raw value: " . @statsFields[0];
}
else
{
	print "Retry was " . $retryCount . "\n";
}

#always send heartbeat
$url=$baseUrl . $heartbeatApiKey . "&" . $hbField . "=" . trim(@statsFields[1]);
$url=$url . "&" . $hbDeviceStateField . "=" . trim(@statsFields[34]);
$url=$url . "&" . $hbErrField . "=" . trim(@statsFields[35]);
sendUrl($url);

if (($reportNow==0) && !sysopen( dataFile, $datFile, O_RDONLY))
{
	print "Unable to open dat file $datFile\n";
	$reportNow=1;
}
else
{
	#if not forcing stats update then check the existing dat file, exit if
	#it matches the data just got from stats
	foreach my $line (<dataFile>)
	{
		if ( $line eq $stats )
		{
			print "Data has not updated, exiting now....\n";
			exit;
		}
	}
}
close (dataFile);


#vend stats first
$url=$baseUrl . $vendStatsApiKey . "&";
$url=$url . $vsVendCountField . "=" . trim(@statsFields[1]);
$url=$url . "&" . $vsVendsRemainingField . "=" . trim(@statsFields[18]);
$url=$url . "&" . $vsEmergancyVendsField . "=" . trim(@statsFields[19]);
$url=$url . "&" . $vsCashBoxField . "=" . trim(@statsFields[23]);
$url=$url . "&" . $vsTotalCashField . "=" . trim(@statsFields[22]);
$url=$url . "&" . $vsScaleFactorField . "=" . trim(@statsFields[33]);
sendUrl($url);

#hoper counts
$url=$baseUrl . $hopperStatsApiKey . "&";
$url = $url . $hs1Field . "=" . trim(@statsFields[2]);
$url = $url . "&" . $hs2Field . "=" . trim(@statsFields[4]);
$url = $url . "&" . $hs3Field . "=" . trim(@statsFields[6]);
$url = $url . "&" . $hs4Field . "=" . trim(@statsFields[8]);
$url = $url . "&" . $hs5Field . "=" . trim(@statsFields[10]);
$url = $url . "&" . $hs6Field . "=" . trim(@statsFields[12]);
$url = $url . "&" . $hs7Field . "=" . trim(@statsFields[14]);
$url = $url . "&" . $hs8Field . "=" . trim(@statsFields[16]);
sendUrl($url);

#hopper errors
$url=$baseUrl . $hopperErrorApiKey . "&";
$url = $url . $hs1Field . "=" . trim(@statsFields[3]);
$url = $url . "&" . $hs2Field . "=" . trim(@statsFields[5]);
$url = $url . "&" . $hs3Field . "=" . trim(@statsFields[7]);
$url = $url . "&" . $hs4Field . "=" . trim(@statsFields[9]);
$url = $url . "&" . $hs5Field . "=" . trim(@statsFields[11]);
$url = $url . "&" . $hs6Field . "=" . trim(@statsFields[13]);
$url = $url . "&" . $hs7Field . "=" . trim(@statsFields[15]);
$url = $url . "&" . $hs8Field . "=" . trim(@statsFields[17]);
sendUrl($url);

#if we are here dump the data to the data file
open( dataFile, ">", $datFile) or die "Unable to open dat file for save $datFile $!";
print dataFile $stats;
close (dataFile);

exit(0);

#sends the url and checks for non 0 return, dies if it is 0
sub sendUrl()
{
	my $url = shift;
	$dataState = `wget -q -O- \"$url\"` or die "Error sending $url : " . $dataState;
	
	print "url ret=" . $dataState . "\n";
	
	if ($dataState == 0)
	{
		
		die "Error sending $url : " . $dataState;
	}
}