Project Overview

I primarily use Perl for most of my Raspberry Pi projects, and with a growing number of quality Pi modules on CPAN and an upcoming book on Perl and Raspberry Pi programming, I expect Perl's popularity on the Pi to increase. That being said, I was bored, and wanted to use some left over components I had laying around to make my own home security system from scratch. It's in its early stages right now, and I will be adding more features later.

The Idea

The idea is that all the sensors in the system will send their status to a centralized server via UDP messages. The server will log all the messages in text and csv format. The user interface will parse the logs using Perl regular expressions and the DBD::CSV cpan module to create graphs on the fly.

Front door sensor

The door sensor consist of a Pi Zero W and a magnetic door contact. Whenever the door is opened, it sends a UDP message to the server with a Unix Epoch timestamp. When closed, it sends another UDP message to the server which then calculates the time it was opened.

Centralized UDP Server

The status report script takes a message string as an argument and sends it's status to the server using backticks like so: `perl sndstat.pl "Door is opened!"`;

#!/usr/bin/perl -w use strict; use IO::Socket; my($sock, $msgserver, $msg, $port, $ipaddr, $msghost, $MAXLEN, $PORTNO, $TIMEOUT); $MAXLEN = 1024; $PORTNO = 8597; $TIMEOUT = 5; $msghost = "192.168.0.7"; $msg = $ARGV[0]; print "MSG: $msg

"; $sock = IO::Socket::INET->new(Proto => 'udp', PeerPort => $PORTNO, PeerAddr => $msghost) or die "cannot create socket $!"; $sock->send($msg) or die "SEND: $!";

The server accepts UDP messages from all the sensors in the system using the following script:

#!/usr/bin/perl -w use strict; use IO::Socket; my ($sock, $statmsg, $devaddr, $devhost, $MAXLEN, $PORTNO); $MAXLEN = 1024; $PORTNO = 8597; $sock = IO::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp') or die "socket: $@"; #open log file for writing open CQ, ">>", "security_.log" or die $!; #autoflush file handle select((select(CQ), $| = 1)[0]); print "awaiting UPD messages from security devices on $PORTNO...

"; while($sock->recv($statmsg, $MAXLEN)){ my ($port, $ipaddr) = sockaddr_in($sock->peername); $devhost = gethostbyaddr($ipaddr, AF_INET); print gmtime()."$devhost--> $statmsg

"; #$sock->send("received stat ok-> $statmsg"); print CQ gmtime().",$devhost--> $statmsg

"; }#end while close CQ; die "recv: $!";

The door sensor contains a TMP36 temperature sensor, and an MCP3208 12-bit ADC to read its value.

Inside door sensor

On the front porch, I used a general-purpose data logger (made with Pi Zero W) I had from another project to take temperature readings and send them to the server. It also uses the TMP36 on an external PCB and an MCP3208 ACD to read it.

Front door temperature sensor

I wrote my own GPIO file system interface in Perl that uses a GPIO numbering that makes sense to me. Using that script, I wrote a bit-bang driver for the MCP3208 ADC chip.

My Perl GPIO mapping

Perl GPIO interface script:

#!/usr/bin/perl -w use strict; =pod gpio utilities for RPi enable, disable, read, write =cut #global vars #rpi my @line_nums = (2,3,4,17,27,22,10,9,11,5,6,13,19,26,14,15,18,23,24,25,8,7,12,16,20,21); my ($iox, $status); sub gpio_enable{ my $ind = $_[0]; $iox = $line_nums[$ind]; #print "IO line: $iox

"; my $gpiosz = @line_nums; #print "gpiosz: $gpiosz

"; my $direction = $_[1]; #print "direction: $direction

"; #err chk if($ind < 0 || $ind > $gpiosz){ print "INVALID GPIO RANGE

"; exit; }#end if invalid index if($direction ne 'in' && $direction ne 'out'){ print "INVALID DIRECTION

"; exit; }#end if invalid direction #write gpio pin val to export file open(EF, ">", "/sys/class/gpio/export") or die $!; print EF $iox; close(EF); #set direction of gpio pin open(GF, ">", "/sys/class/gpio/gpio$iox/direction") or die $!; print GF $direction; close(GF); }#end gpio_enable sub gpio_disable{ }#end gpio_disable sub gpio_read{ #gpio number as arg, returns direction in/out my $ind = $_[0]; $iox = $line_nums[$ind]; my $value = ''; #reads state of gpio pin open(GS, "/sys/class/gpio/gpio$iox/value") or die $!; while(<GS>){ $value = $_; }; #print "$iox val: $value"; close(GS); if($value == 0 || $value == 1){ return $value; }#end unless else{ print "ERROR: Undefined value on GPIO $iox

"; exit; }#end else }#end gpio_read sub gpio_write{ my $ind = $_[0]; $iox = $line_nums[$ind]; my $value = $_[1]; #write value to gpio pin open(GV, ">", "/sys/class/gpio/gpio$iox/value") or die $!; print GV $value; close(GV); }#end gpio_write 1;

Using the previous GPIO control script, here is the bit-bang MCP3208 driver:

#!/usr/bin/perl use strict; use Time::HiRes qw (usleep); require "/home/pi/gpio/gpio_chip.pl"; =pod routines for MCP3208 4-channel analog to digital converter =cut #print "MCP3208

-------

"; #SPI / bitbang varables my ($clk, $din, $dout, $chpsel); #channels: first bit is set to single, not differential #5 bits because the first is the start bit my @ch0 = (1,1,0,0,0); my @ch1 = (1,1,0,0,1); my @ch2 = (1,1,0,1,0); my @ch3 = (1,1,0,1,1); my @ch4 = (1,1,1,0,0); my @ch5 = (1,1,1,0,1); my @ch6 = (1,1,1,1,0); my @ch7 = (1,1,1,1,1); #differential my @ch01 = (1,0,0,0,0);#a0+, a1- my @ch02 = (1,0,0,1,0);#a2+, a3- sub read3208{; #init empty array that will #contain 10-bit reading from ADC my @reading = (); #get reading from the MCP3208 ADC #starts comm when cs goes from high to low #ADC channel number #channel is an array reference my $channel = $_[0]; #delay between clock pulses #needs to operate at 10KHz so #sample is accurate, so min usleep(100) my $delay = $_[1]; #reference voltate, used only for calculations #can be omitted my $vref = $_[2]; #main clock variable my $i=0; #high flag for data gpio line my $hf = 0; #number of clock pulses, my $ncp = 38; #for 20 clock pulses #high or low state of clock pulse my $state = 0; my $seed = 3; #seed used to determine high or low, start 3 so first pulse is high #toggles CS line to init communication with the adc #start low, go high, then low. comm begins when CS #brought from high to low gpio_write($chpsel, 0); usleep($delay); #main loop while($i<$ncp){ $state = $seed%2;#toggles between 1 and 0 $seed++; #clock pulse high if(($i%2) eq 0){ #print "i: $i\tcp high\tstate: $state

"; if($channel->[$i/2] eq 1 && $i <=8){ gpio_write($din, 1); $hf = 1;#set high flag #print "ch[".($i/2)."]: ".$channel->[$i/2]."

"; }#end if if($channel->[$i/2] eq 0 && $i <=8){ gpio_write($din,0); }#end if zero on data line }#end if high #if($i>8){#data line #everything after D0 bit is don't care, #set to state #gpio_write($din, $state); #}#end else #clock pulse gpio_write($clk, $state); #read data out #data clocked out on falling #edge of clk if(($i%2) == 1 && $i >12){ #read state of gpio pin connected #to data out of ADC push @reading, gpio_read($dout); #print "i[$i]: ".$reading[($i-14)/2]."

" }#end read data out #lower data if high flag set if($hf eq 1){ #gpio_write($din,0); $hf = 0;#reset high flag }#end if #pause between clk pls usleep($delay); #increment counter $i++; }#end while #make din low gpio_write($din, 0); #make chip select high gpio_write($chpsel, 1); #perform calcuations, return a voltage #based of 10-bit reading, and vref val my $bindig = 2048; my $rdgval = 0; my $voltage = 0; for(my $x=0; $x<12;$x++){ if(@reading[$x] == 1){ $rdgval = $rdgval + $bindig; }#end if $bindig = $bindig / 2; }#end for #voltage calculation $voltage = ($rdgval*$vref)/4096; return (\@reading, $rdgval, $voltage); }#end read3208 sub init3208{ =pod called by program before you can use the 3208 pass the gpio lines you wish to use =cut $clk = $_[0]; #clock $din = $_[1]; #instructions to adc $dout = $_[2]; #10-bit output from adc $chpsel = $_[3]; #latch / load to init comm to adc #init gpio lines gpio_enable($clk, 'out'); gpio_enable($din, 'out'); gpio_enable($dout, 'in'); gpio_enable($chpsel, 'out'); #init chpsel high, comm begins when #chipsel goes from high to low gpio_write($chpsel, 1); usleep(100); }#end init3208 sub clkpls{ gpio_enable(7, 'out'); my $i=0; while($i<1000){ gpio_write(7,1); usleep(50000); gpio_write(7,0); $i++; }#end while gpio_write(7,0); }#end

Here is the script on front porch temperature sensor:

#!/usr/bin/perl use strict; require "MCP3208.pl"; #channels: first bit is set to single, not differential #5 bits because the first is the start bit my @ch0 = (1,1,0,0,0); my @ch1 = (1,1,0,0,1); my @ch2 = (1,1,0,1,0); my @ch3 = (1,1,0,1,1); #init the MCP3208 init3208(10,11,12,13); my $i=0; open RD, ">", "temperature.log" or die $!; my $s = 0; for(;;){ #read ADC channel 0 my ($reading, $binval, $voltage ) = read3208(\@ch0, 50, 4.977); #calculate temperature farenheit my $temp = ((($voltage * 1000) - 500) / 10) * 1.8 + 32; $temp = sprintf("%5.2f", $temp); my $tm = gmtime(); #send status to UDP server `perl sndstat.pl "outside temp: $temp"`; #display reading on terminal print "binval: $binval\tvoltage: $voltage vdc\tTemp F: $temp

"; #write to log print RD "$s

$voltage

"; #wait 5 minutes usleep(300000000); } close RD;

Here are 24hrs of results from the front door (outside) temperature sensor:

Front door Temperature readings 24 hrs

Here is the live logging on the UDP server:

Live logging on UDP server

Next, I plan on adding cameras to the door sensors. The UDP server already has the LAMP stack up and running, so I am going to write a front end in PHP that I can view from anywhere. Boredom cured... for now.