Thursday, August 13, 2020

Raspberry Pi motion detection and notification program

PiDoorCam updated

This is the amended, latest version of my Raspberry Pi program to watch the street outside, detect change, and send a picture to another computer for storage, and to my phone. I'm often in the back garden, and can't hear the doorbell; missing deliveries is rather annoying!

It no longer tries to take pictures when it's dark, not just because I don't have an infra-red camera and infra-red floodlights, but mainly because the street light outside flashes on and off all night, and I don't want hundreds of pictures of that!

You'll need to set up a Pushover account, which is free provided you don't send too many notifications, and a Ramdisk with a directory called /var/tmp on the Pi.

Program listing

# Program for PiDoorCam

# Detects motion, and when it spots some, takes a high resolution
# picture, and sends the picture to another computer, also
# a notification via Pushover, to my phone.

import io
import os
import picamera
import ftplib
import time
import datetime
from PIL import Image
import requests
import json
import schedule

camera = picamera.PiCamera()
picamera.PiCamera.CAPTURE_TIMEOUT = 30

# If we detect 100 pixels that changed by 30, we have seen movement.
pixels = 100
difference = 30

# Use the maximum resolution of the camera.
# This is for V1. V2 is 3280 x 2464.
# It's also correct for the ZeroCam.
width = 2592
height = 1944

# Internet lookup of sunrise and sunset at our location
def sunrise_sunset():
    global sunrise, sunset
    
    # Location of greenhouse is lat = yyyyy lon = xxxxx
    url = 'https://api.sunrise-sunset.org/json?lat=yyyyy&lng=xxxxx'
    response = requests.get(url)
    dict = response.json()
    res = dict.get('results')

    curtim = datetime.datetime.now()
    hm = res.get('sunrise').split(":")
    sunrise = curtim.replace(hour=int(hm[0])-1, minute=int(hm[1]))
    print("An hour before sunrise ",sunrise)
    
    hm = res.get('sunset').split(":")
    sunset = curtim.replace(hour=int(hm[0])+13, minute=int(hm[1]))
    print("An hour after sunset   ",sunset)

# I copied this voodoo motion detection from somewhere. Changed the timeout
#  setting above to prevent the occasional failures to complete captures.
# Only alter this if you know what you are doing!
def compare():
   camera.resolution = (100, 75)
   stream = io.BytesIO()
   format = 'bmp'
   camera.capture(stream, format)
   stream.seek(0)
   im = Image.open(stream)
   buffer = im.load()
   stream.close()
   return im, buffer

# Function to take a new high resolution picture, send it to another computer,
# send it to my phone, and then delete it.
def newimage(width, height):
    when = datetime.datetime.now()
    filename = "door-%04d%02d%02d-%02d%02d%02d.jpg" \
               % (when.year, when.month, when.day, when.hour, when.minute, when.second)
    camera.resolution = (width, height)
    camera.capture("/var/tmp/"+filename)

    connected = True
    ftp = ftplib.FTP()
    ftp.connect("computer-name")
    
    try:
        ftp.login("user-name","password")
    except ftplib.all_errors:
        connected = False
        print ("Failed to login to server.")
        ftp.quit()
        
    if connected:
        ftp.storbinary('STOR '+filename, open("/var/tmp/"+filename, "rb"))
        print ("Sent to server ", filename)

    ftp.quit()

# Code to send the Pushover message. Make picture smaller first.
# Note this uses a Ramdisk you must set up elsewhere.
    im = Image.open("/var/tmp/"+filename)
    im.resize((324,243),Image.ANTIALIAS)
    im.save("/var/tmp/"+filename)
    
    r = requests.post("https://api.pushover.net/1/messages.json", data = {
        "token": "you-need-to-get-a-token-from-pushover",
        "user": "you-need-to-get-a-user-name-from-pushover",
        "device": "your-device",
        "sound": "intermission",
        "message": filename
    },
    files = {
        "attachment": (filename, open("/var/tmp/"+filename, "rb"), "image/jpeg")
    })
   # Check r for problems - maybe put a delay here?
    if r.status_code != 200:
        print("Pushover message failed.")
    else:
        print("Pushover accepted the message.")
         
# Now delete the file.
    os.remove("/var/tmp/"+filename)
    # Delay to avoid being nasty to Pushover server.
    time.sleep(5)

# Main program.

camera.rotation = 0
print("Running door.py")
image1, buffer1 = compare()

# Find sunrise and sunset times at two in the morning, and once
# at startup.
schedule.every().day.at("02:00").do(sunrise_sunset)
sunrise_sunset()

while (True):
   # See if it's time to get sunrise and sunset.
   schedule.run_pending()

   image2, buffer2 = compare()

   changedpixels = 0
   for x in range(0, 100):
      for y in range(0, 75):
         pixdiff = abs(buffer1[x,y][1] - buffer2[x,y][1])
         if pixdiff > difference:
            changedpixels += 1

   # See if we think something moved.
   if changedpixels > pixels:
   # See if it's light enough to take a picture.
      now = datetime.datetime.now()
      if now > sunrise and now < sunset:
          newimage(width, height)
      else:
          print("A bit dark at ",now)

   image1 = image2
   buffer1 = buffer2

No comments: