rpiarduinomusings

Raspberry Pi, Arduino, Sensors and the data they produce

A Python sensor program for Raspberry Pi

Introduction

This page presents a Python program that is designed to be a proximity sensor operating on a local network using WIFI. This sensor employs a Passive Infrared sensor (PIR), an LED to give a visual indication that movement has been detected by the PIR, a Picamera and the program presented here.

When the program, PIRLEDeMail.py detects movement, it will turn on the green LED, take a photo using the camera and then send an email with the jpg file attached to the intended recipient.

 

About PIRLEDeMail.py

Each physical component, i.e. PIR, LED, etc, have their own programming requirements which are pretty simple to harness after a little bit of experience with them. I have learned to utilize Python functions to isolate areas of responsibility to make maintenance easier and less complicated. (Repeat after me: “Always code for maintenance”.)

Variable names are used for the GPIO pin assignments and on/off states to keep the code clear of obscure numbers. For example:

#========================================
# Setup LED and PIR pin number assignment
#========================================
LED_GREEN = 3
SENSOR_PIR = 11

#========================================
# Setup Boolean variables
#========================================
OFF=0
ON=1

The green LED was used during development to give me a visual indication that the PIR was picking up motion. It isn’t required for the system to work. In fact, if you want a clandestine sensor, it should be removed because it will only draw attention to the sensor.

During the initialization phase the program will read a text file designed to contain some configuration data for making the required connection to the email provider. You will need to change the entries of this configuration file for your provider. The name of the text file used is ‘/home/pi/PythonDev/sendEmail.txt‘. For my system it is stored in the same development directory as the program. The path to its location should also be changed for your file system.

 
sendEmail.txt

 
[sendEmailConfig]
emailAddress = alfredeneuman@emailprovider.com
username = user
password = SECRET

 
PIRLEDeMail.py

#********************************************************************** # Program : PIRLEDeMail.py # Date : 20160526 # Uses Modules : Yes # # To run this script : python PythonDev/PIRLEDeMail.py # # Version 01 # This program is designed to sense movement using a PIR. When movement is # detected it will turn on the LED. # # Version 02 # Sends eMail by using the SendEmail module. # # Version 03 # This program now runs as a service when the Pi is powered on # # Version 04 # Pi Camera attached, captures image to file and emails it to recipient. # # Version 05 # Added SafeConfigParser to read config file values # #********************************************************************** from datetime import datetime import calendar #import datetime import time #import SendEmail # Module import RPi.GPIO as GPIO import picamera import os import smtplib from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from ConfigParser import SafeConfigParser #======================================== # Setup LED and PIR pin number assignment #======================================== LED_GREEN = 3 SENSOR_PIR = 11 OFF=0 ON=1 #======================================== # GPIO Prep #======================================== GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) GPIO.cleanup() GPIO.setup(SENSOR_PIR, GPIO.IN) #Read output from PIR motion sensor GPIO.setup(LED_GREEN, GPIO.OUT) #LED output pin #======================================= # Camera prep #======================================= camera = picamera.PiCamera() #Valid range for iso is 0-800, unless exposure mode is set #to 'sports' in which case the maximum allowed is 1200. camera.iso = 1200 camera.exposure_mode = 'sports' camera.resolution = (512, 384) camera.framerate = 2 # per second #camera.shutter_speed = 6000000 # Default is 0 which is auto camera.meter_mode = 'spot' # 'matrix' #path = "/home/pi/PiImages/" path = "/mnt/Data/PiPhotos/" filePrefix = "image" fileName="notInitialized" emailAddress = "notInitialized" password = "notInitialized" #********************************************************************** #***** FUNCTION DECLARATIONS ***** #********************************************************************** def readConfigFileValues(): #use the global variables global emailAddress global password parser = SafeConfigParser() parser.read('/home/pi/PythonDev/sendEmail.txt') emailAddress = parser.get('sendEmailConfig', 'emailAddress') password = parser.get('sendEmailConfig', 'password') def takePhoto(camera): global path global fileName # saving the picture to a stream a still shot #stream = io.BytesIO() # Take the picture and store it d = datetime.now() imageTime = d.strftime("%m%d%Y%H%M%S") fileName = filePrefix + imageTime + ".jpg" pictureFile = open(path + fileName, 'wb') #pictureFile = open(os.path(path + fileName), 'wb') #time.sleep(1) camera.capture(pictureFile) pictureFile.close() return fileName def sendMail(msgText, imageData): #use the global variables global emailAddress global password print "Sending email" UserName = emailAddress UserPassword = password FromAddr = UserName ToAddr = UserName msg = MIMEMultipart() msg['Subject'] = msgText msg['From'] = UserName msg['To'] = UserName text = MIMEText(msgText) msg.attach(text) image = MIMEImage(imageData, name = os.path.basename(fileName) ) #image = MIMEImage(imageData, name = os.path(fileName)) msg.attach(image) s = smtplib.SMTP('smtp.gmail.com',587) s.ehlo() s.starttls() s.ehlo() s.login(UserName, UserPassword) s.sendmail(FromAddr, ToAddr, msg.as_string()) s.quit() def ageAndDelete(): path = "/mnt/Data/PiPhotos" for file in os.listdir(path): fullpath = os.path.join(path,file) # turns 'file1.txt' into '/path/to/file1.txt' timestamp = os.stat(fullpath).st_ctime # get timestamp of file createtime = datetime.fromtimestamp(timestamp) now = datetime.now() delta = now - createtime #print "fullpath = "+fullpath #print "file = "+file #print "now = "+str(now) #print "delta = "+str(delta) #print "delta.days = "+str(delta.days) #print if delta.days > 120: #print fullpath os.remove(fullpath) def inRange(value, low, high): intVal = int(value) intLow = int(low) intHigh = int(high) if intLow <= intHigh: return intLow <= intVal <= intHigh else: return intLow <= intVal or intVal <= intHigh #********************************************************************** #***** M A I N L I N E C O N T R O L L E R ***** #********************************************************************** readConfigFileValues() print emailAddress print password #======================================== #Loop to sense movement #======================================== #Give me a minute to leave print "Waiting for 10 seconds, so leave now." time.sleep(10) # Delay for 10 seconds print "Initial delay is completed" testDate = datetime.now() #print str(testDate) #print str(testDate.weekday()) #print str(testDate.hour) photoCounter = 0 while True: #======================================== # Be active # Monday-Friday, 5am-4pm or # Monday-Friday, 9pm-12pm or # Saturday-Sunday, 9pm to midnight or # Saturday-Sunday, midnight to 5am #======================================== doWork = False d = datetime.now() #print str(d.weekday()) #print str(d.hour) #range #start: Starting number of the sequence. #stop: Generate numbers up to, but not including this number. wd = str(d.weekday() ) hr = str( d.hour ) if((inRange(wd,0,4) and inRange(hr,5,16)) or (inRange(wd,0,4) and inRange(hr,21,23)) or (inRange(wd,0,4) and inRange(hr,0,5)) or (inRange(wd,5,6) and inRange(hr,21,23)) or (inRange(wd,5,6) and inRange(hr,0,6)) ): doWork = True #print "Current time is within the schedule to be active." #if Sunday at 6am, age and delete the files if ( d.weekday() == 6 and d.hour == 6 and d.minute == 0 and d.second == 1 and d.microsecond < 0000001): ageAndDelete() #print doWork #Uncomment this line when testing so that the schedule is bypassed #Don't forget to comment the code when deploying to production. #doWork = True #Get the value for pin 11 i=GPIO.input(SENSOR_PIR) if i==OFF: #When output from motion sensor is LOW #print "No intruders",i GPIO.output(LED_GREEN, OFF) #Turn OFF LED time.sleep(0.1) elif i==ON: #When output from motion sensor is HIGH #Perform the work if True if doWork: #GPIO.output(LED_GREEN, ON) #Turn ON LED print "Movement detected on " + d.strftime("%A %B %d, %Y %X %Z") msg = 'Movement in Zone 01 on ' + d.strftime("%A %B %d, %Y %X %Z") #Take the photo using the camera object. The file will be written #to the HD and the name of the file is returned. fileName = takePhoto(camera) time.sleep(1) #prepare jpg attachment by reading it from the HD, written there #by the "takePhoto(camera) function, above. fp = open(path + fileName, 'rb') imageData = fp.read() fp.close() #Send the email with the msg as the subject and the imageData #as the attachment. sendMail(msg, imageData) time.sleep(3) # Delay for n seconds to stop taking pictures #********************************************************************** #********************************************************************** #***** E N D O F S O U R C E ***** #********************************************************************** #**********************************************************************

 

The functions of the program

The following functions are used to separate areas of responsibility.

  • readConfigFileValues()
    This function reads a text file that contains externalized name/value pairs. Externalizing such values makes it easy to modify them without having to change the code. In this case the values relate to the email provider and the user id and password used to establish the connection. The contents of this file look similar to this :

  • takePhoto(camera)
    This function is called when a photo is to be taken. It passes the object that is derived from Picamera (camera = picamera.PiCamera()) when the program went through its variable initialization phase.

  • sendMail(msgText, imageData)
    This function sends an email with the message text along with the photo to be attached.

  • ageAndDelete()
    This is a housekeeping type of function. Since the pictures are stored onto disk they will accumulate over time. It isn’t necessary to preserve them forever so this function was created to go through the directory of photos and age them. If too old, say over 90 days or so, the photo is deleted and disk space is reclaimed.

  • inRange(value, low, high)
    This is a convenience function designed to make the writing of if-statements more manageable and less prone to errors. It will accept a value to be compared to a low range and high range. The function returns a boolean to indicate if the value falls between the established range.

 

The main processing loop

The main loop is responsible for determining if motion was sensed by the PIR sensor. If movement is detected it will take a photo and call the sendMail function which will attach it to the email and send it to the recipient defined in the configuration file for the program.

The elephant in the room is that if-statement. If the current time in hours fall between the specified range, PIR state sensing will occur and therefore a picture will be taken and sent to the recipient by email. Since the if-statement is designed to limit when this work is to be done, you will want to modify it to fit your requirements.

 
       wd = str(d.weekday() )
       hr = str( d.hour )

       if((inRange(wd,0,4) and inRange(hr,5,15) ) or
          (inRange(wd,0,4) and inRange(hr,21,23) ) or
          (inRange(wd,0,4) and inRange(hr,0,5) ) or
          (inRange(wd,5,6) and inRange(hr,21,23) ) or
          (inRange(wd,5,6) and inRange(hr,0,6)) ):

 

Limitations

The only limitation is with regard to the Picamera. The version I employed is not good for night time photography since there is no flash attached. There are other cameras better suited for this regime.

Furthermore, I discovered that if the PIR is set behind a typical window screen, it will not sense movement on the other side of the window. This could be further complicated by the fact that said window is of the double pane variety and the void is filled with an inert gas.

 

Summary

This program has been “in production” on two Raspberry Pi’s with a third to follow. Basic setup is relatively easy. I have chosen to make this program a service, launched by Supervisor when the Pi is turned on. For information about how to install and configure Supervisor, see this link and go to the section “Install Supervisor and config“. Note that the instructions there reference this program, so they have been vetted.

Leave a comment