Home alarm on Rpi + PiFace Digital V2


Raspbian Bullseye and Motion 4.0


Features:
-video stream from usb cameras
-motion detection and automatic image recording
-control via web browser
-input with delay (front door)
-tamper line input
-sends a email message of alarm activity
-after rebooting the system is set to the last state of alarm
-login any activity alarm 

We will need a few useful programs:
$ sudo apt-get install monit
$ sudo apt-get install screen
$ sudo apt-get install chkconfig

Install Motion for cameras:
$ sudo apt-get install motion
$ sudo chmod 777 /etc/motion/motion.conf
$ sudo mkdir /home/pi/ftp
$ sudo chmod 777 /home/pi/ftp
$ sudo mkdir /home/pi/ftp/cam1
$ sudo chmod 777 /home/pi/ftp/cam1
$ sudo mkdir /home/pi/ftp/cam2
$ sudo chmod 777 /home/pi/ftp/cam2

Install pifacedigitalio
$ sudo apt install python3-pip
$ sudo pip3 install pifacecommon
$ sudo pip3 install pifacedigitalio

It is good to once every few days to restart the system, and once a day, you can execute the command:
sudo sh -c "sync; echo 3 > /proc/sys/vm/drop_caches"

We can, of course, write the script run from Cron:
$ sudo mkdir /usr/local/bin/myservice
$ sudo nano /usr/local/bin/myservice/freemem.sh
#!/bin/sh
sudo sh -c "sync; echo 3 > /proc/sys/vm/drop_caches"

we set the permissions:
$ chmod 755 freemem.sh

we must edit crontab:
$ sudo nano /etc/crontab
10 12   * * 7   root    sudo /sbin/reboot
1 12    * * 1-7 root    /usr/local/bin/myservice/freemem.sh

Description of electrical connections PiFace Digital:
Output 0 - external siren.
Output 1 - internal siren for tamper line.
Output 2 - logical "1" = alarm is disarm,  logical "0" = alarm is arm.
Input 0 - delayed line.
Input 1 - line without delay.
Input  2 - arm.
Input 3 - disarm.
Input 5 - tamper line input.
Input 6 - arm status (arm/disarm).
Input 7 - alarm status (active/inactive).

Connect in_5 to 'NO' relay K0, use shottky diode BYV10-40 in5--|>|--relay / observe polarity!
Connect in_6 to out_2, use shottky diode BYV10-40 in6--|>|--out2 / observe polarity!
Connect in_7 to out_0, use shottky diode BYV10-40 in7--|>|--out0 / observe polarity!
Power for the siren connect to 'C' (common) relay K0 input. The terminal '+' of the siren connect to 'NO' relay K0 input. The mass of the siren to the mass of the system.

Alarm script in python:
Note: If you copy the code, be sure to swap space at the beginning of the line to the appropriate number of tabs!

$ sudo nano /usr/local/bin/myservice/alarm.py

#!/usr/bin/env python3

# PiFace Digital
# home alarm by fuse
# v.2.1
# 14.08.2022
# Python 3
# out 0 - siren
# out 1 - internal siren (sabotage alarm)
# out 2 - logic 1 = disarm alarm, logic 0 = arm alarm
#  in 0 - line A (delayed)
#  in 1 - line B
#  in 2 - arm alarm
#  in 3 - disarm alarm
# connect in_5 to siren relay, use shottky diode BYV10-40 in5--|>|--relay / observe polarity!!!
# connect in_6 to out_2, use shottky diode BYV10-40 in6--|>|--out2 / observe polarity!!!
# connect in_7 to out_0, use shottky diode BYV10-40 in7--|>|--out0 / observe polarity!!!
#  in 5 - sabotage siren line input
#  in 6 - arm status
#  in 7 - alarm status
# http://address:port/?output_port=arm - arm alarm
# http://address:port/?output_port=disarm - disarm alarm
# http://address:port/?output_port=wipe - wipe log file
# http://address:port/?output_port=rst - reboot rpi

import sys
import subprocess
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse
import urllib.request
import pifacedigitalio as alarm
from threading import Timer
import time
import logging
import argparse
import smtplib, ssl
from email.mime.text import MIMEText
from os import system

msg_email = 'email@gmail.com'
msg_pwd = 'password'
msg = MIMEText('Alarm email system.')
msg['From'] = msg_email
msg['To'] = msg_email
msg['Subject'] = 'Alarm - START'

try:
    logging.basicConfig(filename='/var/www/alarmpy.log', format='%(asctime)s %(message)s', datefmt='%y/%m/%d %H:%M:%S', level=logging.INFO)
except:
    logging.basicConfig(filename='/var/tmp/alarmpy.log', format='%(asctime)s %(message)s', datefmt='%y/%m/%d %H:%M:%S', level=logging.INFO)

password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

alarm.init()

JSON_FORMAT = "{{\"Arm\":{input6},\"Sir\":{output0},\"Act\":{output8}}}"#,\"Ca1\":{output9}}}"
DEFAULT_PORT = 8000
OUTPUT_PORT_GET_STRING = "output_port"
GET_IP_CMD = "hostname -I"

class PiFaceWebHandler(BaseHTTPRequestHandler):
    def do_HEAD(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_AUTHHEAD(self):
        pass
        #self.send_response(401)
        #self.send_header('WWW-Authenticate', 'Basic realm=\"Alarm\"')
        #self.send_header('Content-type', 'text/html')
        #self.end_headers()

    def do_POST(self):
        pass

    def do_COPY(self):
        pass

    def do_SELECT(self):
        pass

    def do_INPUT(self):
        pass

    def do_CONFIGURE(self):
        pass

    def do_PUT(self):
        pass

    def do_DELETE(self):
        pass

    def do_SET(self):
        pass

    # handles PiFace web control requests
    def do_GET(self):
        # present frontpage with user authentication
        if self.headers['Authorization'] == None:
            pass
            #self.do_AUTHHEAD()
            #self.wfile.write(bytes('no auth header received', 'UTF-8'))
        elif self.headers['Authorization'] == 'Basic xyz': # xyz = user:password encode in base64
            output_value = self.pifacedigital.output_port.value
            input_value = self.pifacedigital.input_port.value
            # parse the query string
            query_components=""
            if "?output_port=" in self.path:
                qs = urllib.parse.urlparse(self.path).query
                query_components = urllib.parse.parse_qs(qs)
            # set the output
            if OUTPUT_PORT_GET_STRING in query_components:
                new_output_value = output_value
                if query_components["output_port"][0] == "arm":
                    new_output_value = 4
                    logging.info ('ARM')
                    try:
                        #send_mail_event('ARM - alarm system')
                        f1 = open('/var/log/alarmstatus.txt','w')
                        f1.write('1')
                        f1.close()
                    except:
                        logging.info ('ERR:write_status_1')
                elif query_components["output_port"][0] == "disarm":
                    new_output_value = 0
                    logging.info ('DISARM')
                    try:
                        #send_mail_event('DISARM - alarm system')
                        f1 = open('/var/log/alarmstatus.txt','w')
                        f1.write('0')
                        f1.close()
                    except:
                        logging.info ('ERR:write_status_0')
                    try:
                        f2 = open('/var/log/alarmactive.txt','w')
                        f2.write('0')
                        f2.close()
                    except:
                        logging.info ('ERR:write_active_0')
                elif query_components["output_port"][0] == "wipe":
                    try:
                        open("/var/www/alarmpy.log","w").close()
                    except:
                        logging.info ('ERR:wipe')
                elif query_components["output_port"][0] == "rst":
                    try:
                        logging.info ('REBOOT')
                        command = "/usr/bin/sudo /sbin/reboot"
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
                        output = process.communicate()[0]
                        print (output)
                    except:
                        logging.info ('ERR:reboot')
                else:
                    logging.info ('COMMAND:unknown')
                output_value = self.set_output_port(new_output_value, output_value)
            output_value = self.pifacedigital.output_port.value
            input_value = self.pifacedigital.input_port.value
            output_value0 = output_value & 0b00000001
            if output_value0 != 0:
                output_value0 = 1
            input_value6 = input_value & 0b01000000
            if input_value6 != 0:
                input_value6 = 1
            try:
                f2r = open('/var/log/alarmactive.txt','r')
                act_value = f2r.read(1)
                f2r.close()
            except:
                act_value = 2
            # reply with JSON
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(bytes(JSON_FORMAT.format(input6=input_value6,output0=output_value0,output8=act_value,), 'UTF-8'))
        else:
            pass
            #self.do_AUTHHEAD()
            #self.wfile.write(bytes('not authenticated', 'UTF-8'))

    def set_output_port(self, new_value, old_value=0):
        # sets the output port value to new_value, defaults to old_value
        port_value = old_value
        try:
            port_value = int(new_value)  # dec
        except ValueError:
            port_value = int(new_value, 16)  # hex
        finally:
            self.pifacedigital.output_port.value = port_value
            return port_value

def get_my_ip():
    # returns this computers IP address as a string
    ip = subprocess.check_output(GET_IP_CMD, shell=True).decode('utf-8')[:-1]
    return ip.strip()

def turn_on_alarm_a(event):
    try:
        if alarm.digital_read(6):
            if not alarm.digital_read(7):
                logging.info ('A:delay_on')
                ta_delay = Timer(10.0, delay_line_a) # set the length of delay for line A
                ta_delay.start()
            else:
                logging.info ('A:already_on')
    except:
        logging.info ('ERR:turn_on_alarm_a')

def turn_on_alarm_b(event):
    try:
        if alarm.digital_read(6):
            if not alarm.digital_read(7):
                event.chip.leds[0].turn_on()
                logging.info ('B:on')
                tb = Timer(60.0, turn_off_alarm_auto_timer) # set the length of alarm for line B
                tb.start()
            else:
                logging.info ('B:already_on')
            try:
                send_mail_event('ACTIVE B! - alarm system')
                f2ab = open('/var/log/alarmactive.txt','w')
                f2ab.write('1')
                f2ab.close()
            except:
                logging.info ('ERR:write_active_1')
    except:
        logging.info ('ERR:turn_on_alarm_b')

def disarm_alarm(event):
    try:
        if alarm.digital_read(5):
            event.chip.leds[1].turn_off()
            logging.info ('S:ok')
        else:
            logging.info ('S:active')
        if alarm.digital_read(6):
            event.chip.leds[0].turn_off()
            event.chip.leds[2].turn_off()
            logging.info ('DISARM')
            try:
                #send_mail_event('DISARM - alarm system')
                f1a = open('/var/log/alarmstatus.txt','w')
                f1a.write('0')
                f1a.close()
            except:
                logging.info ('ERR:write_status_0')
            try:
                f2a = open('/var/log/alarmactive.txt','w')
                f2a.write('0')
                f2a.close()
            except:
                logging.info ('ERR:write_active_0')
        else:
            logging.info ('DISARM:already')
            try:
                f2a = open('/var/log/alarmactive.txt','w')
                f2a.write('0')
                f2a.close()
            except:
                logging.info ('ERR:write_active_0')
    except:
        logging.info ('ERR:disarm')

def arm_alarm(event):
    try:
        if not alarm.digital_read(6):
            logging.info ('ARM:will_be')
            time.sleep(1)
            event.chip.leds[0].turn_off()
            event.chip.leds[2].turn_on()
            logging.info ('ARM')
            try:
                #send_mail_event('ARM - alarm system')
                f1a = open('/var/log/alarmstatus.txt','w')
                f1a.write('1')
                f1a.close()
            except:
                logging.info ('ERR:write_status_1')
        else:
            logging.info ('ARM:already')
    except:
        logging.info ('ERR:arm')

def delay_line_a():
    try:
        if alarm.digital_read(6):
            pifacedigital.leds[0].turn_on()
            logging.info ('A:on')
            ta = Timer(60.0, turn_off_alarm_auto_timer) # set the length of alarm for line A
            ta.start()
        try:
            send_mail_event('ACTIVE A! - alarm system')
            f2aa = open('/var/log/alarmactive.txt','w')
            f2aa.write('1')
            f2aa.close()
        except:
            logging.info ('ERR:write_active_1')
    except:
        logging.info ('ERR:delay_line_a')

def turn_off_alarm_auto_timer():
    try:
        if alarm.digital_read(7):
            pifacedigital.leds[0].turn_off()
            logging.info ('AUTOTIMER:off')
    except:
        logging.info ('ERR:turn_off_auto_timer')

def send_mail_after_start_timer():
    send_mail_event('Alarm - START')

def sabotage_line(event):
    try:
        if not alarm.digital_read(7):
            if not alarm.digital_read(5):
                event.chip.leds[1].turn_on()
                logging.info ('S:on')
    except:
        logging.info ('ERR:s_on')

def send_mail_event(msg_subject):
    logging.info (msg_subject)
    msg = MIMEText('Alarm email system.')
    msg['From'] = msg_email
    msg['To'] = msg_email
    msg['Subject'] = msg_subject
    try:
        ssl_context = ssl.create_default_context()
        server = smtplib.SMTP_SSL('smtp.gmail.com', 465, context=ssl_context)
        server.login(msg_email, msg_pwd)
        server.sendmail(msg_email, msg_email, msg.as_string())
        server.quit()
    except Exception as e:
        logging.info (str(e)) #('Alarm:err_send_mail')

if __name__ == "__main__":
    # get the port
    if len(sys.argv) > 1:
        port = int(sys.argv[1])
    else:
        port = DEFAULT_PORT

    # set up PiFace Digital
    PiFaceWebHandler.pifacedigital = alarm.PiFaceDigital()

    logging.info ('BOOT')

    # run the server
    server_address = ('', port)
    pifacedigital = alarm.PiFaceDigital()
    listener = alarm.InputEventListener(chip=pifacedigital)
    listener.register(0, alarm.IODIR_RISING_EDGE, turn_on_alarm_a) # _RISING_ for reverse logic, _FALLING_ for normal logic
    listener.register(1, alarm.IODIR_RISING_EDGE, turn_on_alarm_b) # _RISING_ for reverse logic, _FALLING_ for normal logic
    listener.register(5, alarm.IODIR_RISING_EDGE, sabotage_line)
    listener.register(2, alarm.IODIR_FALLING_EDGE, arm_alarm)
    listener.register(3, alarm.IODIR_FALLING_EDGE, disarm_alarm)
    listener.activate()

    try:
        ts = Timer(25.0, send_mail_after_start_timer)
        ts.start()
        f = open('/var/log/alarmstatus.txt','r')
        status = f.read(1)
        if status == "1":
            try:
                pifacedigital.output_port.value = 0x04
                logging.info ('ARM:at_boot')
            except:
                logging.info ('ERR:arm_at_boot')
        else:
            try:
                pifacedigital.output_port.value = 0x00
                logging.info ('DISARM:at_boot')
            except:
                logging.info ('ERR:disarm_at_boot')
    except:
        logging.info ('BOOT:unknow_status')

    try:
        httpd = HTTPServer(server_address, PiFaceWebHandler)
        httpd.serve_forever()
    except KeyboardInterrupt:
        logging.info ('^C received, shutting down server')
        httpd.socket.close()


we set the permissions:
$ chmod 755 alarm.py

$ sudo nano /lib/systemd/system/alarm.service
[Unit]
Description=Alarm Service
After=multi-user.target
[Service]
Type=idle
User=pi
ExecStart=sudo /usr/bin/python /usr/local/bin/myservice/alarm.py
Restart=always
RestartSec=0


$ sudo chmod 644 /lib/systemd/system/alarm.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable alarm.service

[Install]
WantedBy=multi-user.target

Zoom and unzoom script for Microsoft LifeCam Cinema
$ sudo nano /usr/local/bin/myservice/zoom.sh

#!/bin/sh

v4l2-ctl -d /dev/video0 --set-ctrl zoom_absolute=10
v4l2-ctl -d /dev/video0 --set-ctrl pan_absolute=115200
v4l2-ctl -d /dev/video0 --set-ctrl tilt_absolute=-28000

we set the permissions:
$ chmod 755 zoom.sh

$ sudo nano /usr/local/bin/myservice/unzoom.sh

#!/bin/sh

v4l2-ctl -d /dev/video0 --set-ctrl zoom_absolute=0
v4l2-ctl -d /dev/video0 --set-ctrl pan_absolute=0
v4l2-ctl -d /dev/video0 --set-ctrl tilt_absolute=0

we set the permissions:
$ chmod 755 unzoom.sh

Web pages - all files must be in the /var/www:

index.html:
<html>
<head>
</head>
<body>
<body style="width:600px">
<iframe src="status.html" name="status" width="600px" height="25px" frameBorder="0" marginwidth="0" marginheight="0">
  <p>Your browser does not support iframes.</p>
</iframe>
<div>
<iframe src="button.html" name="button" width="130px" height="640px" frameBorder="0" marginwidth="0" marginheight="0">
  <p>Your browser does not support iframes.</p>
</iframe>
<iframe src="http://camerastreamurl:8081" name="camera" width="360px" height="640px" frameBorder="0" marginwidth="0" marginheight="0">
  <p>Your browser does not support iframes.</p>
</iframe>
</div>
</body>
</html>

status.html:
<html>
<head>
<meta http-equiv="refresh" content="2">
</head>
<body>
<body style="width:550px">
<iframe src="http://yoururl:8000" name="status" width="550px" height="25px" frameBorder="0">
  <p>Your browser does not support iframes.</p>
</iframe>
</body>
</html>

button.html:
<html>
<head>
</head>
<body>
<body style="width:125px">
<iframe src="" name="empty" style="display:none;"></iframe>
<iframe src="" name="empty1" style="display:none;"></iframe>
<iframe src="" name="empty2" style="display:none;"></iframe>
<iframe src="" name="empty3" style="display:none;"></iframe>
<form action="http://yoururl:8000" method="get" target="empty">
<div>
<input type="submit" name="output_port" value="disarm" style="height:50;width:120;font-weight:bold;">
<input type="submit" name="output_port" value="arm" style="height:50;width:120;font-weight:bold;">
<a href="http://yoururl:8080/0/detection/start" target="empty1"><input type="button" value="start cam1" style="height:50;width:120;font-weight:bold;"></a>
<a href="http://yoururl:8080/0/detection/pause" target="empty2"><input type="button" value="pause cam1" style="height:50;width:120;font-weight:bold;"></a>
<a href="http://yoururl:8080/0/action/snapshot" target="empty3"><input type="button" value="snapshot cam1" style="height:50;width:120;font-weight:bold;"></a>
<a href="alarmpy.log" target="_blank"><input type="button" value="event log" style="height:50; width:120; font-weight:bold;"></a>
<input type="submit" name="output_port" value="wipe" style="height:50; width:120; font-weight:bold;">
<input type="submit" name="output_port" value="zoom" style="height:50; width:120; font-weight:bold;">
<input type="submit" name="output_port" value="unzoom" style="height:50; width:120; font-weight:bold;">
<input type="submit" name="output_port" value="rst" style="height:50; width:120; font-weight:bold;">
</div>
</form>
</body>
</html>

Comments

Popular Posts