Dustin La Ferney
Published © CC BY-NC-SA

Perl+Pi DIY Home Security

Yet another DIY home security project with Raspberry Pi and the Perl programming language. Sensors report status to server via UDP messages.

BeginnerWork in progress4 hours2,851
Perl+Pi DIY Home Security

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
Temperature Sensor
Temperature Sensor
×1
MCP3208
×1
SB404 Prototype PCB
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Male Header 40 Position 1 Row (0.1")
Male Header 40 Position 1 Row (0.1")
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Door Entry device

Shows connections to Pi zero, magnetic door switch, and 16X4 LCD

Temperature sensor

TMP36 read by MCP3208 ADC.

Code

MCP3208.pl Bit-bang ADC driver

Perl
Driver for the MCP3208 12-bit ACD. Include it in your script to use the functions.
#!/usr/bin/perl
use strict;
use Time::HiRes qw (usleep);
require "/home/pi/gpio/gpio_pi.pl";

=pod
routines for MCP3208 4-channel
analog to digital converter
=cut

#print "MCP3208\n-------\n";

#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\n";

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

Perl GPIO driver for Raspberry Pi

Perl
Perl script that controls the file system interface of Raspberry Pi's. Include in scripts to use the functions.
#!/usr/bin/perl -w
use strict;

=pod
gpio utilities for RPi
enable, disable, read, write
=cut

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\n";

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

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

	#err chk
	if($ind < 0 || $ind > $gpiosz){
		print "INVALID GPIO RANGE\n";
		exit;
	}#end if invalid index
	if($direction ne 'in' && $direction ne 'out'){
		print "INVALID DIRECTION\n";
		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\n";
		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;

Security device send status UDP socket

Perl
Sends status of security device to UDP server. Takes status string as an argument.
#!/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\n"; 
$sock = IO::Socket::INET->new(Proto => 'udp', PeerPort => $PORTNO, PeerAddr => $msghost) or die "cannot create socket $!";

$sock->send($msg) or die "SEND: $!";

UDP server for security system

Perl
Logs messages from security devices. Listens on UDP socket.
#!/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...\n";
while($sock->recv($statmsg, $MAXLEN)){
	my ($port, $ipaddr) = sockaddr_in($sock->peername);
	$devhost = gethostbyaddr($ipaddr, AF_INET);
	print gmtime()."$devhost--> $statmsg\n";
	#$sock->send("received stat ok-> $statmsg");
	print CQ gmtime().",$devhost--> $statmsg\n";
}#end while

close CQ;
die "recv: $!";

Door entry alarm

Perl
Perl script that reports to the UDP server when the front door is opened and closed.
#!/usr/bin/perl -w
use strict;
require "gpio_pi.pl";

gpio_enable(2, 'in');
my $state = 0;		#open or closed
my $msg = '';		#msg for log file, lcd	
my $t = '';		#time string
my $oflg = 0;		#open flag
my $cflg = 0;		#closed flag
my $ot = 0;		#open time epoch
my $ct = 0;		#close time epoch
my $days = 0;		#day duration
my $hours = 0;		#hour duration
my $minutes = 0;	#minutes duration
my $seconds = 0;	#seconds duration

#open log file append
open FD, ">>", "frontdoor.txt" or die $!;

for(;;){

	$state = gpio_read(2);	

	#time for log and lcd
	$t = localtime;
	$t =~ s/^....//;
	$t =~ s/2018//;

	if($state == 1){
		if($oflg ==0){	
			#print to lcd
			$msg = "Door Opened!    $t";
			`./lcd "$msg"`;
			`perl sndstat.pl "front door opened $t"`;

			#epoch time for calc
			$ot = time();

			#print to log
			print FD "door opened $t, $ot\n";
		}#end if open flag low

		#set open flag high
		$oflg = 1;		
	}#end if
	else{
		if($oflg == 1){
			#clear lcd
			`./lcd ""`;
		
			#set flags
			$oflg = 0;

			#time & calculations
			$ct = time();
			my $nsec = $ct - $ot;
			my $duration = '';
			if($nsec > 86400){#days
				$days = int($nsec / 86400);			
				$duration = "$days days ";
				$nsec = $nsec - ($days * 86400);
			}#end if days
			if($nsec > 3600){#hours
				$hours = int($nsec / 3600);
				$duration = $duration."$hours hours ";
				$nsec = $nsec - ($hours * 3600);
			}#end if hours
			if($nsec > 60){#minutes	
				$minutes = int($nsec / 60);
				$duration = $duration."$minutes minutes ";
				$nsec = $nsec - ($minutes * 60);
			}#end if minutes
			if($nsec < 60){#seconds
				$seconds = $nsec;
				$duration = $duration."$seconds seconds\n";
			}#end seconds
			#print to log file
			print FD "door closed $t, $ct\n";
			print FD $duration;
			print FD "------------------------------------------\n";
			$duration =~ s/\n//;

			`perl sndstat.pl "front door closed $t DURATION: $duration"`;
		}#end if
		
		$cflg = 1;

		#flush file handle, no buffering
		select((select(FD), $|=1)[0]);
	}#end else
	sleep(1);
}#end for

close FD;

C executable to control 16X4 LCD used in this project

C/C++
Need to compile lcd.c like so: gcc -o lcd lcd.c -lwiringPi -lwiringPiDev . Executable takes a string as a command line argument, then displays on the LCD
#include <stdio.h>
#include <wiringPi.h>          
#include <lcd.h>               
 
//USE WIRINGPI PIN NUMBERS
#define LCD_RS  25               //Register select pin
#define LCD_E   24               //Enable Pin
#define LCD_D4  23               //Data pin 4
#define LCD_D5  22               //Data pin 5
#define LCD_D6  21               //Data pin 6
#define LCD_D7  14               //Data pin 7
 
int main(int argc, char** argv)
{
    int lcd;               
    wiringPiSetup();        
    lcd = lcdInit (2, 16, 4, LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7, 0, 0, 0, 0);

    lcdPosition(lcd, 0, 0);

    printf("nargs: %d \n", argc); 
    printf("lcdout: %s \n", argv[1]);
 
    lcdPuts(lcd, argv[1]);   
}

Credits

Dustin La Ferney

Dustin La Ferney

2 projects • 1 follower

Comments