Raspberry Pi, Arduino, Sensors and the data they produce

A Python Zone Sentry Program


This post presents ZoneSentry.py. As the name implies it is a sentry application that uses a Passive Infrared (PIR) sensor in the same manner as the PIRLEDeMail.py program does. ZoneSentry.py is a total rewrite and renders PIRLEDeMail.py obsolete.

The purpose of ZoneSentry.py is to provide monitoring capability for specific zones of a home and during specific times of day.

Movement is sensed by the PIR which starts the process of referencing the monitoring schedule assigned to the zone. This schedule data is stored in a property file (modTimeRange.txt) making it easy to change scheduling when necessary.


ModTimeRange.py and its property file

ZoneSentry.py imports a module called ModTimeRange.py that is responsible for performing time range testing. The process is rather detailed and encapsulating the logic into a module hides all that detail and makes the processing reusable in other programs.

To perform time range testing, ModTimeRange.py first reads and stores related properties that are stored in modTimeRange.txt. It contains name/value pairs that govern program behavior. See below for a sample of these values.


Properties for ModTimeRange.py
The properties listed below are assigned to the “[general]” section of modTimeRange.txt. This section name and the others that follow must not be changed as the program is hard-coded to reference these names. The same holds for the property names.

activeSchedule” names the schedule that the ModTimeRange.py is to use. This provides the ability create multiple schedules as needed.

configReadFreq” contains the value that tells the program how frequently it is to reread the property file. This provides the ability to make changes to properties on the fly without having to restart the Raspberry Pi. The value shown in the example file below tells ModTimeRange.py to read the file every hour.

printTimeInfo” tells the program to print the file’s contents to the console during program startup. If running the Raspberry Pi in a headless state, set this to False as shown. True will print the contents to the console.

The properties listed below are assigned to the “[schedule01]”, “[schedule02]” and “[scheduleTest]” sections of modTimeRange.txt. The names for schedules can be anything you like. Just be sure that the name assigned to the schedule, matches the value for the property “activeSchedule”, described above.

“Monday” through “Sunday” The values assigned here relate to time ranges for a given day. The only requirement here is to be sure they are supplied as shown, that is, time ranges are separated by a single dash. Each range is separated by a single comma. Spaces are not allowed. There can be any number of ranges for a given day.

[general] activeSchedule = scheduleTest configReadFreq = 010000 printTimeInfo = False [schedule01] Monday = 000000-041500,051500-160000,211500-235959 Tuesday = 000000-041500,051500-160000,211500-235959 Wednesday = 000000-041500,051500-160000,211500-235959 Thursday = 000000-041500,051500-160000,211500-235959 Friday = 000000-041500,051500-160000,235959-235959 Saturday = 000000-050000,220000-235959 Sunday = 000000-050000,220000-235959 [schedule02] Monday = 000000-041500,051500-160000,211500-235959 Tuesday = 000000-041500,051500-160000,211500-235959 Wednesday = 000000-041500,051500-160000,211500-235959 Thursday = 000000-041500,051500-160000,211500-235959 Friday = 000000-041500,051500-160000,211500-235959 Saturday = 000000-050000,220000-235959 Sunday = 000000-050000,220000-235959 [scheduleTest] Monday = None Tuesday = None WednesDay = None Thursday = None Friday = None Saturday = 000000-235959 Sunday = 000000-235959


ZoneSentry.py and its property file

This section presents the subject of this post, “ZoneSentry.py”.

Generally, this program operates on the schedule entered into the properties file explained in the previous section. When motion is detected, it refers to the schedule for the current day. If the current time falls between one of the provided ranges for the current day, it will proceed with further processing of the movement event.

Processing of the event entails taking a picture, storing it in a directory of the file system, creating an email and attaching the photo to it and sending the email on its way. The recipient can then make further decisions regarding the content.

Rather than give detailed descriptions of what each section does and because the program is simple and commented, I’ll leave that task to the reader.

#!/usr/bin/python #********************************************************************** # Program : ZoneSentry.py # Date : 20160901 #********************************************************************** import os import sys import platform import time import datetime from datetime import datetime import RPi.GPIO as GPIO import picamera #Custom modules import ModSendEmail import ModTimeRange from ConfigParser import SafeConfigParser import threading #********************************************************************** #***** Variables ***** #********************************************************************** #======================================== # Setup pin number assignments #======================================== SENSOR_PIR = 16 RELAY = 11 #======================================== # GPIO Prep #======================================== GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) GPIO.cleanup() GPIO.setup(SENSOR_PIR, GPIO.IN) GPIO.setup(RELAY, GPIO.OUT) #======================================= # 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' #======================================= # Image file paths #======================================= path = "PiPhotos/" filePrefix = "image" fileName="notInitialized" #======================================= # Email values #======================================= emailAddress = "notInitialized" password = "notInitialized" #======================================= # Miscellaneous #======================================= OFF=0 ON=1 #======================================= # Property value variable names read from # '/home/pi/PythonDev/sendEmail.txt'# #======================================= alarmStartTime = None relayThreadState = None #======================================= # Property value variable names read from # '/home/pi/PythonDev/zoneSentry.txt' #======================================= useRelayLogic = None zoneID = None #======================================= # Program related data used by main() #======================================= progName = os.path.basename(__file__) # Full path of this python script mypath=os.path.abspath(__file__) # Path location only (excluding script name) baseDir=mypath[0:mypath.rfind("/")+1] baseFileName=mypath[mypath.rfind("/")+1:mypath.rfind(".")] #********************************************************************** #***** FUNCTION DECLARATIONS ***** #********************************************************************** def readConfigFileValues(): #get a SafeConfigParser parser = SafeConfigParser() #read properties for email connection global emailAddress global password configFileHome = '/home/pi/PythonDev/sendEmail.txt' parser.read(configFileHome) emailAddress = parser.get('sendEmailConfig', 'emailAddress') password = parser.get('sendEmailConfig', 'password') #read properties for relay logic use global zoneID global useRelayLogic configFileHome = '/home/pi/PythonDev/zoneSentry.txt' parser.read(configFileHome) useRelayLogic = parser.get('general', 'useRelayLogic') zoneID = parser.get('general', 'zoneID') #---------------------------------------------------------------------- # This function accepts a value to be compared with the low and high # values that are also provided. These values are expressed as integers # and if the value (the first parameter) falls between the low and high # values (inclusive), True will be returned, otherwise False. #---------------------------------------------------------------------- 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 #---------------------------------------------------------------------- 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 motionDetected(SENSOR_PIR): global alarmStartTime global relayThreadState global zoneID #Call the funtion of ModTestRange.py to see if the current time #falls within the range specified for the current day. if ModTimeRange.testTime(): #take photo print("Taking photo") #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. d = datetime.now() msg = zoneID + ' on ' + d.strftime("%A %B %d, %Y %X %Z") ModSendEmail.sendMail(msg, imageData, fileName) time.sleep(1) # Delay for n seconds to stop taking pictures #Turn on relay to sound alarm. if useRelayLogic == "True": if(relayThreadState == None): relayThreadState = "started" print "Turning on relay" GPIO.output(RELAY, GPIO.HIGH) #Start relay timer. This is a thread. t = threading.Thread(target=relayThread) t.start() else: print("Can't take photo right now. If you want one now, edit modTimeRange.txt") #Turn off relay GPIO.output(RELAY, GPIO.LOW) #Use the config file's value assigned to configReadFreq #to re-read the config file for any potential changes. ModTimeRange.checkConfigReadFreq() return #---------------------------------------------------------------------- def relayThread(): global alarmStartTime global relayThreadState secondsToBeActive = 15 #Determine elapsed time if(alarmStartTime == None): alarmStartTime = time.time() monitorElapsedTime = True while(monitorElapsedTime): elapsedTime = time.time() - alarmStartTime #print "Elapsed time " + str(elapsedTime) if(elapsedTime >= secondsToBeActive): print("Alarm has been running for " + str(elapsedTime) + " seconds. Turning off relay") GPIO.output(RELAY, GPIO.LOW) alarmStartTime = None monitorElapsedTime = False relayThreadState = None #---------------------------------------------------------------------- def execute(): #Turn the relay off GPIO.output(RELAY, GPIO.LOW) #Get the email provider and password for connection and call config readConfigFileValues() ModSendEmail.config("smtp.gmail.com", 587, emailAddress, password) #Initialize the Time Range module to read the time ranges for each #day. ModTimeRange.init() #Add an event listener to detect a signal rise for the PIR sensor. #If one is detected, call the function motionDetected(). GPIO.add_event_detect(SENSOR_PIR, GPIO.RISING, callback=motionDetected) #This simply keeps the program running forever. while True: time.sleep(0) return #********************************************************************** #***** M A I N L I N E C O N T R O L L E R ***** #********************************************************************** if __name__ == '__main__': print("+++++++++++++++++++++++++++++++++++") print("%s - Entering Program" % progName) print "Path to program = " + mypath print sys.version_info print "The O/S = " + platform.system() try: execute() finally: print("%s - Exiting Program" % progName) print("+++++++++++++++++++++++++++++++++++") print("") #********************************************************************** #********************************************************************** #***** E N D O F S O U R C E ***** #********************************************************************** #**********************************************************************

Properties for ZoneSentry.py
Shown below is the property file (zoneSentry.txt) that is used by ZoneSentry.py. There isn’t much to this one. The property “zoneID” names the zone that is used in the email subject line. The property value for “useRelayLogic” is a boolean (True/False values only) that governs the use of the logic that controls a relay that may be attached to the sensor.

[general] zoneID=Zone 01 ( useRelayLogic=True

One response to “A Python Zone Sentry Program

  1. Nathanael Peabody May 31, 2017 at 5:49 pm

    Great site you have here.. It’s hard to find excellent writing like yours these days. I truly appreciate people like you! Take care!!|


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: