Door bell with pi camera and motion detection

Yeah its the official 2.1 version.

Just found another bug in my doorbell script.
Very rarely if you press the button while the motion has triggered it is trying to take two stills at once.
It raises an error and breaks out.

picamera.exc.PiCameraValueError: source port is already connected
sys.excepthook is missing
lost sys.stderr
sys.excepthook is missing
lost sys.stderr
sys.excepthook is missing
lost sys.stderr

Need to check from each instance (motion and button) before the still is captured.
Just a matter of finding a way to implement it.

Python Try, except?

Iā€™m aware of exceptional handling but not bothered to learn it yetā€¦

Think I cracked it.
Now if a snapshot is taking place and the other event calls the subroutine, it wont take a snapshot, just copy and rename the most recent (which at this stage would be the image currently being captured)

import numpy as np
import picamera
import picamera.array
from time import gmtime,strftime
import time
import datetime
import subprocess
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import RPi.GPIO as GPIO
import os 
import shutil
# Smart doorbell script adapted by Lee Goodman (Meganoodle from example online
# https://www.modmypi.com/blog/raspberry-pi-gpio-sensing-motion-detection
# Detects motion from pi camera and Sends MQTT message to Home assistant
# Detects Button press from GPIO 23 and sends MQTT to home assistant
# COMPONENTS
#
# Raspberry  Pi zero W
# Raspberry Pi Camera V2
# 
# Door bell button..Momentary (normally open)
# Wifi Dongle or wifi capable pi
#
# SOFTWARE
# Latest version of Raspian 
#
# Camera and GPIO must be enabled in raspi-config
#
# GPIO Pinouts
#=======================================
# Door Bell Button-----Raspberry Pi
# Feed-----------------GND
# Load-----------------GPIO 23 (PIN 16)
#=======================================
#
# Setup ================================
# MQTT Variables
auth = {
  'username':"pi",
  'password':"raspberry"
}
hostip="192.168.0.8"
# File Path
ImagePath='/home/pi/doorbell/camera/'
# Motion detection state
MotionState="0"
# Doorbell button GPIO SETUP
GPIO.setmode(GPIO.BCM)
DB_PIN = 23
GPIO.setup(DB_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Doorbell button state variable
DBState="0"
SNAPPING=False

# Functions =============================
# Check dir structure and create if necessary
def CheckDir(dirchk):
    chkpath='./'+dirchk
    if os.path.isdir(chkpath)==False:
        print'Creating '+dirchk+' directory...'
        os.mkdir(chkpath)
        if os.path.isdir(chkpath)==False:
            print'Error creating directory please check the drive is full and your path permissions'
        else:
            print dirchk+' directory created successfully...'
    else:
        print'Found '+dirchk+' directory...'

# Check if 'doorapp.conf' exists and create a blank file if necessary
# To be included into programme
def ChkConf():
    if os.path.isfile('./conf/door.conf')==False:
        print'Creating blank configuration file...'
        fconf = open('./conf/door.conf','w')
        print >>fconf,'[MQTT HOST]=192.168.0.8'
        print >>fconf,'[MQTT PASSWORD]=Anon'
        print >>fconf,'[MQTT USERNAME]=Anon'
        fconf.close()
        print'Configuration file created, please edit "./conf/door.conf"'
    else:
        print 'Configuration file found.'

def Addlog(loginfo):
    logdate=datetime.datetime.now().strftime('%b/%d/%G')
    logtime=datetime.datetime.now().strftime('%I:%M:%S%p')
    logentry=logdate+" : "+logtime+" : "+loginfo+"--"
    flog = open('./doorbell.log','a+')
    print >>flog,logentry
    flog.close
    
# Doorbell pressed function
def DOORBELLON(DB_PIN):
    #if motion is triggered
    if GPIO.input(DB_PIN)<>1:
        DBState="1"
        PublishMQTT("doorbell/button",DBState)
        print "Doorbell Pressed..."
        Addlog('Doorbell Pressed.')
    #if doorbell is released  
    if GPIO.input(DB_PIN)<>0:
        DBState="0"
        PublishMQTT("doorbell/button",DBState)
        print "Doorbell Released..."
        Addlog('Doorbell Released.')
        SNAPSHOT(0)
        
# Take a snapsots ( file name is the date and time .jpg )       
def SNAPSHOT(Pressed):
    global SNAPPING
    
    print 'Snapping...'
    Tstamp = ImagePath+'DB'+datetime.datetime.now().strftime('%b-%d-%G_%I%M%S%p')+'.jpg'
    if SNAPPING == False:
        SNAPPING = True
        camera.capture ('./camera/recent.jpg')
        time.sleep(1)
        SNAPPING = False
        
    if Pressed ==0:
        Tstamp = ImagePath+'DB-'+datetime.datetime.now().strftime('%b-%d-%G_%I%M%S%p')+'.jpg'
        shutil.copy('./camera/recent.jpg',Tstamp)        
        print 'Snapshot Saved to "'+Tstamp+'"'
    print'Snapshot complete'
    Tstamp = ImagePath+'MD-'+datetime.datetime.now().strftime('%b-%d-%G_%I%M%S%p')+'.jpg'
    if Pressed ==1:
        shutil.copy('./camera/recent.jpg',Tstamp)        
        print 'Snapshot Saved to "'+Tstamp+'"'
    print'Snapshot complete'
        
# Send MQTT payload
def PublishMQTT(topic,payload):
    publish.single(topic,
                   payload=payload,
                   hostname=hostip,
                   client_id="doorbell",
                   auth=auth,
                   port=1883)

# PICAM Motion detection class    
class DetectMotion(picamera.array.PiMotionAnalysis):
    def analyse(self, a):
        global MotionState
        a = np.sqrt(
            np.square(a['x'].astype(np.float)) +
            np.square(a['y'].astype(np.float))
            ).clip(0, 255).astype(np.uint8)
        # If there're more than 10 vectors with a magnitude greater
        # than 60, then say we've detected motion
        # and send MQTT motion state to HA
        if (a > 60).sum() > 10:
            print('Motion detected.')
            Addlog('Motion detected.')
            SNAPSHOT(1)
            MotionState="1"
            PublishMQTT("doorbell/motion",MotionState)
        else:
            # If Motion has only just turned passive send MQTT passive state to HA
           if MotionState == "1":
               print "Motion Passive."
               Addlog('Motion Passive.')
               MotionState="0"
               PublishMQTT("doorbell/motion",MotionState)

# Main ========================
# Initial Setup
print "Smart Doorbell ... (CTRL+C to exit)"
print "Checking directory stucture..."
CheckDir('camera')
time.sleep(2)
CheckDir('conf')
print " Directory structure check complete."
print "Checking for configuration file..."
ChkConf()
print " Configuration file check complete."

# GPIO Interrupt setup
try:               
    GPIO.add_event_detect(DB_PIN,GPIO.BOTH, callback=DOORBELLON, bouncetime=200 )
# Main Loop                      
    while 1:

   
        with picamera.PiCamera() as camera:
            with DetectMotion(camera) as output:
                camera.resolution = (640, 480)
                camera.vflip = True
                camera.framerate = 20
                camera.start_recording('/dev/null', format='h264', motion_output=output)
                camera.wait_recording(86400)
                camera.stop_recording()
                

            
except KeyboardInterrupt:
    print " Quit"
    GPIO.cleanup()

Thanksā€¦Looks quite simple and I will probably see if I can include it, Im sure as the code grows there will be more to handle .
Just scanning through it , it looks simple enough to implement.

lol just an afterthought have it send any errors it flags up to my phone, at least it can make me aware, rather than having to ssh to check.

Done a bit more prototyping, to see what kind of image I can get through the peep viewer barrel.

With the lens on the barrel, I get a wider field of view and the image isnā€™t great, but probably OK.

If I remove the fish eye lens off the barrel I get a better image but a much smaller field of view. This would require the person at the door to be standing directly in front of the camera.

Overall I think the wider field of view is more desirable, so will try that. Now itā€™s just a case of mounting the pi camera up against the peep hole, and fine tuning the focus of the pi lens to improve the image.

1 Like

I agree, Im not sure if you could improve it much more , unless the camera was mounted on the inner part of the external panel of the door.
Im guessing thats not going to be easy or not such a desirable option.

1 Like

Iā€™m planning on using this case, with some modificationsā€¦

A nice article, this opeCV route must be possible :slight_smile: https://www.hackster.io/luisomoreau/who-is-at-the-coffee-machine-72f36c

are you aware about this board?
OpenMV
seems promising

1 Like

Wow
Will have to research this a bit more

it is cheaper than RPi3 and camera module together and it is far more intelligent and robust solution I think

1 Like

Ill have a read of this, I think this was one of the guides I followed on my pi 2 originally,

But Ill try again at the weekend.

(Im training at work this week so need to focus on that atm).

Thanks openMV looks like a great platform, particularly for an optics person like myself. Iā€™ve got something similar arriving soon https://www.kickstarter.com/projects/1602548140/jevois-open-source-quad-core-smart-machine-vision

1 Like

Wow just watched the video , looks amazing!

to be honest I donā€™t really trust the crowdfunding campaigns anymore unless it is already on the market
Iā€™ve lost my money couple of times on the past and I am not confident anymore on supporting small projects :disappointed:

Got it working finally.

import cv2
from picamera import PiCamera
import picamera.array

camera = PiCamera()
camera.vflip = True
#camera.resolution = (640, 480)
#detect faces from haarcascade xml reference file
#returns relevent location data of faces 
def detect(path):
    img = cv2.imread(path)
    cascade = cv2.CascadeClassifier("./haarcascade_frontalface_default.xml")
    rects = cascade.detectMultiScale(img, 1.3, 4, cv2.cv.CV_HAAR_SCALE_IMAGE, (20,20))

    if len(rects) == 0:
        return [], img
    rects[:, 2:] += rects[:, :2]
    return rects, img
# draws rectangle around detected faces and saves image
def box(rects, img):
    ci=1
    for x1, y1, x2, y2 in rects:
        cv2.rectangle(img, (x1-5, y1-5), (x2+5, y2+5), (127, 255, 0), 2)
        sub_face = img[y1:y2, x1:x2]
        face_file_name = "./face_" + str(ci) + ".jpg"
        cv2.imwrite(face_file_name, sub_face)
        ci=ci+1
    cv2.imwrite('./detected.jpg', img);
         
camera.capture ('./one.jpg')
rects, img = detect("./one.jpg")
print "faces Detected = "+str(len(rects))
box(rects, img)

It crops the detected faces too

1 Like

Perfect! It works real time on the camera feed?

nice!
you seem a bit tired though :slight_smile:

Nice! Im by far not a developer/programmer but a ā€˜tinkererā€™. Ive got a zero W coming from overseas as still cant get in australia.
What guide did you use to get cv installed?