04 декабря 2016

Система безопасности на базе Raspberry Pi

Оповещение о проникновении через telegram.

Прошло несколько месяцев с момента начала эксплуатации решения Rasp + Gdrive. Появилась идея добавить небольшой функционал. В итоге я перенес скрипт на Python, полностью его переписал и собрал из него демон для системы.





c
Итак задача:
1. Как и первой версии скрипта, пишем на Google Disk склеенные с 2-х камер снимки с заданным интервалом(раз в 5 минут).
2. Если в нерабочее время на снимках замечено движение- отправлять оповещение через telegram.

Посколько нет смысла сравнивать снимки сделанные с интервалом в 5 или 10 минут. За это время вор может остаться незамеченным. Поэтому я полностью переписал логику работы скрипта.

Скрипт запускается как сервис. В рабочее время(то есть когда нет необходимости в оповещении о проникновении) снимки создаются раз в 5 минут. 
В нерабочее интервал создания снимков- 15 секунд. Сравнение 2-х последних снимков, на предмет наличия изменений и оповещение через telegram, если изменения будут обнаружены(с отправкой снимка).
Итак:
1. Оповещения отправляются через бота телеграм. Что это и как создать хорошо описано на просторах сети. Создать можно на сайте "папаши"
2. Скрипт запускается как демон с помощью supervisor
3. Чтобы не насиловать SD карту малины, все файлы я пишу на подключенную USB флешку.

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image, ImageChops
import numpy as np
import datetime, time, os, shutil, telepot
#-----------------------------------------------------------
INTPHOTO = 5 # Интервал сохранения снимков на гуглодиск

now = datetime.datetime.now()

ROOTDIRECTORY = "/mnt/USB/PragaVoices/"
TMPDIRECTORY = "/mnt/USB/tmp"
FAILSCOUNT = 0
ALERTSTATUS = 0
CONTROLFILE = TMPDIRECTORY + '/control.jpeg'

WORKTIMEST = datetime.time(10,00)
WORKTIMEEN = datetime.time(20,00)
ALERTTIMEST = datetime.time(21,30)
ALERTTIMEEN = datetime.time(8,00)

#-----------------------------------------------------------
def motion_detect(image1, image2):
 im1 = Image.open(image1)
 im2 = Image.open(image2)
 img = ImageChops.difference(im2, im1)
 w,h = img.size
 a = np.array(img.convert('RGB')).reshape((w*h,3))
 h,e = np.histogramdd(a, bins=(16,)*3, range=((0,256),)*3)
 prob = h/np.sum(h) # normalize
 prob = prob[prob>0] # remove zeros
 image_ent = -np.sum(prob*np.log2(prob))
 im1.close()
 im2.close()
 if image_ent >= 1:
  return True
 else:
  return False
#-----------------------------------------------------------
def newest_file_in_tree(rootfolder, extension=".jpeg"):
 return max(
  (os.path.join(dirname, filename)
  for dirname, dirnames, filenames in os.walk(rootfolder)
  for filename in filenames
  if filename.endswith(extension)),
  key=lambda fn: os.stat(fn).st_mtime)
#-----------------------------------------------------------
def get_photo_from_cam(DATE, TIME):
 for i in range(1, 3):
  response = os.system("ping -c 2 192.168.1.25" + str(i))
  if response == 0:
   cmd = 'avconv -rtsp_transport tcp -i "rtsp://192.168.1.25' + str(i) + '/user=user&password=password&channel=1&stream=0.sdp" -f image2 -vframes 1 -pix_fmt yuvj420p ' +  TMPDIRECTORY + '/' + TIME + '-CAM' + str(i) + '.jpg'
   os.system(cmd)
 if os.path.isfile(FILE1) and os.path.isfile(FILE2):
  cmd = 'convert +append -strip -interlace Plane -quality 60 ' + FILE1 + ' ' + FILE2 + ' ' + TMPDIRECTORY + '/' + TIME + '.jpeg'
  os.system(cmd)
  os.remove(FILE1)
  os.remove(FILE2)
  return True
 else:
  return False
#-----------------------------------------------------------
newest = newest_file_in_tree(ROOTDIRECTORY)
if not os.path.isfile(CONTROLFILE):
 shutil.copy(newest, CONTROLFILE)

while True:
 now = datetime.datetime.now()
 now_time = datetime.datetime.now().time()

 if now_time >= ALERTTIMEEN and now_time <= ALERTTIMEST:
  SLEEPINTERVAL = INTPHOTO * 60
 else:
  SLEEPINTERVAL = 15

 DATE = now.strftime("%d-%m")
 TIME = now.strftime("%H-%M")
 DIRECTORY = ROOTDIRECTORY + DATE

 if not os.path.exists(DIRECTORY):
  os.makedirs(DIRECTORY)

 if not os.path.exists(TMPDIRECTORY):
  os.makedirs(TMPDIRECTORY)

 FILE1 = TMPDIRECTORY + '/' + TIME + '-CAM1.jpg'
 FILE2 = TMPDIRECTORY + '/' + TIME + '-CAM2.jpg'
 #-----------------------------------------------------------
 newest = newest_file_in_tree(ROOTDIRECTORY)
 PHOTOPREVTIME = os.stat(newest).st_mtime
 PHOTOPREVTIMECONTROL = os.stat(CONTROLFILE).st_mtime

 if get_photo_from_cam(DATE, TIME):
  PHOTOLASTTIME = os.stat(TMPDIRECTORY + '/' + TIME + '.jpeg').st_mtime
  INTERV = (PHOTOLASTTIME - PHOTOPREVTIME) / 60 
  INTERVCONTROL = (PHOTOLASTTIME - PHOTOPREVTIMECONTROL) / 60
                               
  #-----------Если кто-то пришел- ТРЕВОГА------------
  if now_time >= ALERTTIMEST or now_time <= ALERTTIMEEN:
   if motion_detect(TMPDIRECTORY + '/' + TIME + '.jpeg', CONTROLFILE):
#    shutil.copy(TMPDIRECTORY + '/' + TIME + '.jpeg', DIRECTORY)
    TelegramBot.sendMessage(GROUPID, 'Замечено движение в салоне')
    photo = open(TMPDIRECTORY + '/' + TIME + '.jpeg', 'rb')
    TelegramBot.sendPhoto(GROUPID, photo)
    photo.close()
  #-----------------------------------------------------------
    
  if INTERVCONTROL >= INTPHOTO:
   if os.path.isfile(CONTROLFILE):
    os.remove(CONTROLFILE)
   shutil.copy(TMPDIRECTORY + '/' + TIME + '.jpeg', CONTROLFILE)

  if INTERV >= INTPHOTO:
   if now_time >= WORKTIMEST and now_time <= WORKTIMEEN:
    shutil.copy(TMPDIRECTORY + '/' + TIME + '.jpeg', DIRECTORY)
                                                
   #-----------Если кто-то пришел положить на диск-----------
   if (now_time >= ALERTTIMEEN and now_time <= WORKTIMEST) or (now_time >= WORKTIMEEN and now_time <= ALERTTIMEST):
    if motion_detect(TMPDIRECTORY + '/' + TIME + '.jpeg', CONTROLFILE):
     shutil.copy(TMPDIRECTORY + '/' + TIME + '.jpeg', DIRECTORY)

  os.remove(TMPDIRECTORY + '/' + TIME + '.jpeg')
 else:
  for i in range(1, 3):
   if os.path.isfile("FILE" + str(i)):
    PHOTOLASTTIME = os.stat("FILE" + str(i)).st_mtime
    INTERV = (PHOTOLASTTIME - PHOTOPREVTIME) / 60
    if INTERV >= INTPHOTO and now >= WORKTIMEST and now <= WORKTIMEEN:
     shutil.move("FILE" + str(i), DIRECTORY)
    else:
     os.remove("FILE" + str(i))
  FAILSCOUNT += 1
  
  if FAILSCOUNT >= 5 and ALERTSTATUS == 0:
   TelegramBot.sendMessage(GROUPID, 'Нет изображения с одной из камер. Детектор движения отключен')
   FAILSCOUNT = 0
   ALERTSTATUS = 1

 time.sleep(SLEEPINTERVAL)