RasPI-Surv: a Raspberry-based scalable SMS remote controlled video surveillance system

Good morning dear security systems fanatics Garretlabs fans!

Today you will be very interested in my new post, I’m sure.

Because today we talk about a high-scalable videosurveillance system called RasPI-Surv, based on:

  • A system controller based on Raspberry PI
  • A practical (and modifications-friendly) command/telemetry interface via SMS messages, using the very well known SIM-900 module from ITEAD.
  • A set of wireless video cameras with good motion detection feature
  • A internet router/modem with wireless access point
  • A Google Mail account (it will be your “house account”), to be accessed via IMAP.

I engineered this system after a very strange request from one of my strangest friends. ūüôā

He was looking for a “internal” videosurveillance system with motion detection to be used only during his vacation periods…and after the vacation periods he wanted to disable the system and put all cameras and controller inside a box! ūüėČ

In other words he wanted a “portable” internal video surveillance system, without in-the-wall connections, without fixed positions for the cameras, so a very dynamic system with the opportunity to move the cameras and the controller inside his house. Plus, he wanted a powerful and portable alarm siren to be placed in his house, without external components (please note that this DIY solution is not accepted by italian laws, so in our country the alarm device must be always placed by a devoted an credited technician).

A practical example of the siren 12V powered could be THIS¬† …but you can buy another model following your needs.

Thinking for a smart solution, I studied a system with two day-night wireless IP Cameras from HooToo with pan and tilt features (http://www.hootoo.com/hootoo-ht-ip211hdp-indoor-wireless-network-camera-with-ir-cut-black.html) and two day-night D-Link fixed cameras (http://www.dlink.com/it/it/home-solutions/view/network-cameras/dcs-932l-day-night-cloud-camera). But you could add/remove wireless IP cameras with motion detection featire (also of different brands), in order to add/remove “eyes” to your system.

So, this is a very scalable solution. ūüėČ

Firstly I placed the cameras near a wall power socket (ok, they are wireless..but unfortunately they need power… ūüė¶ ), then I connected all cameras to the internet modem/router following their own user guides.

Finally I enabled their motion detection feature with the email sending (using the “house Google Mail account”) of the captured frame when a motion is detected.

Note that for some camera, a fast tramsition from day to night vision and viceversa (i.e. a sun ray refled by a passing car in the street) could be recognized as motion (so as a false alarm by the system ūüė¶ )…so the best way to place the camera is in front of closed external doors without glasses and of closed windows with the rolling shutter completely closed.

Well… do you placed the alarm siren and cameras? Have you configured your cameras in order to connect to your modem/router and to send an email using your Google mail account when a motion is detected? Very well… we can go ahead with the system idea.

This is the overview schema of the system (as always drawn by hand by me, sorry for the bad photo! ūüėČ ):

wpid-2014-11-10-08.37.09.jpg.jpeg

…In practice at this moment we have all cameras connected to our modem/router and a alarm siren connected to the power thru a relay commanded by the Raspberry PI. So, we must to setup the Raspberry PI in order to execute the following operations/requirements:

  • Periodically it should check the SENT folder of your Google Mail account, in order to verify if one of your IP cameras detected one motion (and so they sent an email using your “home account”). The cycle should be fast because one burglar can be very fast to open a window and to move in your house… ūüė¶
  • If one or more email arrived from your cameras, it should activate the relay in order to power ON the alarm siren
  • In this case it shoud send an SMS to your mobile phone in order to advise you that the siren has been powered ON
  • It should manage a set of SMS commands in order to: force the power OFF of the alarm siren, enable and disable the check of the sent emails, cancel the SMS memory of the SIM900 module, reset or reboot the system etc.
  • It should send a set of SMS in order to advise you that there is some problem (i.e. the internet connection is down, so the Google Mail mailbox can’t be accessed).

Well. It is’nt so difficult to implement it all. If you you read first these previous posts from Garretlabs: THIS ONE (it uses a GSM dongle and the software gammu in order to receive/send the SMS messages, plus to command the GPIO pins of Raspberry using Python) and also THIS ONE (it uses the SIM900 module and the AT commands in order to receive/send the SMS messages).

This is the Fritzing schema of the controller Raspberry PI+SIM900 module used to command the relay for the alarm siren:

Video surveillance controller (finale per uso con SIM900) v2_bb

I propose you the complete code of my project…. so you can now start playing with the RasPI-Surv system

 

#! /usr/bin/env python

import RPi.GPIO as GPIO
import imaplib, re, time, serial, subprocess

#class from http://segfault.in/2010/07/playing-with-python-and-gmail-part-1/ and http://segfault.in/2010/08/playing-with-python-and-gmail-part-2/
class pygmail(object):
    def __init__(self):
        self.IMAP_SERVER='imap.gmail.com'
        self.IMAP_PORT=993
        self.M = None
        self.response = None
        self.mailboxes = []

    def login(self, username, password):
        self.M = imaplib.IMAP4_SSL(self.IMAP_SERVER, self.IMAP_PORT)
        rc, self.response = self.M.login(username, password)
        return rc

    def get_mailboxes(self):
        rc, self.response = self.M.list()
        for item in self.response:
            self.mailboxes.append(item.split()[-1])
        return rc

    def get_mail_count(self, folder='Inbox'):
        rc, self.response = self.M.select(folder)
        return self.response[0]

    def get_unread_count(self, folder='Inbox'):
        rc, self.response = self.M.status(folder, "(UNSEEN)")
        unreadCount = re.search("UNSEEN (\d+)", self.response[0]).group(1)
        return unreadCount

    def get_imap_quota(self):
        quotaStr = self.M.getquotaroot("Inbox")[1][1][0]
        r = re.compile('\d+').findall(quotaStr)
        if r == []:
            r.append(0)
            r.append(0)
        return float(r[1])/1024, float(r[0])/1024

    def get_mails_from(self, uid, folder='Inbox'):
        status, count = self.M.select(folder, readonly=1)
        status, response = self.M.search(None, 'FROM', uid)
        email_ids = [e_id for e_id in response[0].split()]
        return email_ids

    def get_mail_from_id(self, id):
        status, response = self.M.fetch(id, '(body[header.fields (subject)])')
        return response

    def rename_mailbox(self, oldmailbox, newmailbox):
        rc, self.response = self.M.rename(oldmailbox, newmailbox)
        return rc

    def create_mailbox(self, mailbox):
        rc, self.response = self.M.create(mailbox)
        return rc

    def delete_mailbox(self, mailbox):
        rc, self.response = self.M.delete(mailbox)
        return rc

    def logout(self):
        self.M.logout()

#end library/class

##############################################################################
#Functions to PowerON OFF the relay
##############################################################################
#poweron the relay
def PowerONRelay():
    GPIO.setup(22,GPIO.OUT);
    GPIO.output(22,GPIO.HIGH);
    return;

#poweroff the relay
def PowerOFFRelay():
    GPIO.setup(22,GPIO.OUT);
    GPIO.output(22,GPIO.LOW);
    return;

##############################################################################
#Functions to manage SMS
##############################################################################
#check unread arrived messages
def CheckNewUnreadMessage(ser):
    print "Check for new messages..\n";
    ser.write("at\r");
    time.sleep(3);
    line=ser.read(size=64);
    #print line;
    ser.write('AT+CMGL="REC UNREAD"\r')
    time.sleep(3);
    response=ser.read(size=200);
    print response;
    return response;

#function to send confirmation SMS
def SendSMS(ser,value):
    #send SMS about the action
    ser.write("at\r");
    time.sleep(3);
    line=ser.read(size=64);
    print line;
    ser.write('AT+CMGS="+391234567890"\r'); #here put your phone number :-)
    time.sleep(3);
    if (value==1):
        ser.write('ALARM ON!\r');
    elif (value==0):
        ser.write('ALARM OFF!\r');
    elif (value==3):
        ser.write('ALARM enabled!\r');
    elif (value==4):
        ser.write('ALARM disabled!\r');
    elif (value==11):
        ser.write('ALARM commanded OFF (was ON)!\r');
    elif (value==20):
      ser.write('Deleted SIM memory!\r');
        elif (value==50):
      ser.write('INTERNET DOWN!\r');
    elif (value==51):
      ser.write('INTERNET UP!\r');
    elif (value==60):
      ser.write('Warning: GMAIL temporary DOWN!\r');
    time.sleep(3);
    ser.write(chr(26));
    return;

#function to delete messages...(GSM900 stores only 30 messages..)
def DeleteAllMsg(ser):
    ser.write("at\r");
    time.sleep(3);
    line=ser.read(size=64);
    print line;
    print "Deleting memory....";
    ser.write('AT+CMGD=1,4\r');
    time.sleep(3);
    return;    

###############################################################################
#main program for alarm check
###############################################################################
#global variables
initials_sent_emails=0
ALARM_enabled=True
ALARM_ON=False
actual_sent_emails=0;
ALARM_ON_cycles=0;
INTERNET_UP=True;
GMAIL_UP=True;
################################################################################
#functions to enable/disable/stop alarm (sent VIA SMS)
################################################################################
def enable_ALARM():
         global ALARM_enabled
         ALARM_enabled=True
         

def disable_ALARM():
         global ALARM_enabled
         ALARM_enabled=False
         

def stop_ALARM():
        global initials_sent_emails
        global actual_sent_emails
        #ALARM_ON=False
        initials_sent_emails=actual_sent_emails
        #ALARM_ON_cycles=0
        #TODO here send SMS telemetry

################################################################################
#MAIN program
################################################################################

#0: power ON the SIM900 module and waits for SIM registration
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.OUT)
GPIO.output(17,GPIO.HIGH)
print "Registering the sim...\n";
time.sleep(15); #wait for sim to register to the net
#0a: open serial link on /dev/ttyAMA0. It is open only one time:it remains open for all operation time.
#No more open+close action is now requested.
print "Opening communications serial...\n";
ser=serial.Serial('/dev/ttyAMA0',9600,timeout=1);
ser.open();


#1: first login to Gmail
g = pygmail()
g.login('your.home.mailbox@gmail.com', 'yourpassword')
print g.response

#2: list mailboxes
g.get_mailboxes()
for item in g.mailboxes:
  print item

#3: get number of sent emails at power ON of alarm controller...
g.get_mail_count('[Gmail]/Posta inviata')
print g.response
sent_number=g.response[0]
initials_sent_emails=int(sent_number) #actual number of sent messages
print initials_sent_emails

#4:logout from gmail
g.logout();


#Here we add the infinite loop (once per 10 seconds?) which:
#a. checks the number of sent emails (login+get_mails_count+logout)
#b. if the number is > initials_sent_emails sets (&& the alarm is ARMED) the alarm relay ON, else sets the alarm relay OFF
#b2. if this is the first time the alarm is ON, send SMS to communicate the ALARM is correctly ON!
#c. checks if a NEW SMS is arrived in order to enable/disable ALARM relay, and update the global variable
#d: waits for 10 seconds (TBC)
while (True):

       #a0: check for internet status
    ip="www.google.it";
        ret=subprocess.call("ping -c 1 %s" %ip,
        shell=True,
        stdout=open('/dev/null','w'),
        stderr=subprocess.STDOUT)
    if (ret==0):
         print "INTERNET UP!"
        if(INTERNET_UP==False):
            INTERNET_UP=True;
            SendSMS(ser, 51);
    else:
        print "INTERNET DOWN!"
        if (INTERNET_UP==True):
            INTERNET_UP=False;
            SendSMS(ser, 50);

    
       #a: check new sent messages
    if (INTERNET_UP==True):
            print 'Check new sent messages...'
        try:
                g.login('your.home.mailbox@gmail.com', 'yourpassword')
                g.get_mail_count('[Gmail]/Posta inviata')
                actual_sent_emails=int(g.response[0])
                g.logout();
            GMAIL_UP=True
        except imaplib.IMAP4.error, err:
            print 'GMAIL IMAP temporary unavailable!!!'
            if (GMAIL_UP==True):#the first time it sends an sms...
                SendSMS(ser,60)
                GMAIL_UP=False        

        #b/b2: alarm management
        print 'Alarm management...'
        if(ALARM_enabled==1 and actual_sent_emails>initials_sent_emails):
                print 'ALARM ON!!!'
                ALARM_ON=True
                ALARM_ON_cycles=ALARM_ON_cycles+1;
                #here we command the relay ON
                PowerONRelay();
                if (ALARM_ON_cycles==1):
                        SendSMS(ser,1);
                
        else:
                if(ALARM_ON==True):
                        SendSMS(ser,0); #if the alarm is already OFF, dont send messages...
                ALARM_ON=False
                ALARM_ON_cycles=0;
                #here we command the relay OFF
                PowerOFFRelay();
                print 'ALARM OFF!'

        #c: new SMS arrived management
        print 'SMS management'
    message=CheckNewUnreadMessage(ser);
    if(message.find("enablealarm")<>-1):
        enable_ALARM();
        #send message to my phone that Relay is ON
            SendSMS(ser, 3);
        print "Alarm enabled commanded\n";    
    elif (message.find("disablealarm")<>-1):
        disable_ALARM();
        SendSMS(ser, 4);
        print "Alarm disabled commanded\n";
    elif (message.find("alarmoff")<>-1):
        stop_ALARM();
        #send message to my phone that telemetries are OFF
          SendSMS(ser, 11);
        print "Deactivated alarm\n";
        elif (message.find("delsms")<>-1):
        DeleteAllMsg(ser);
        SendSMS(ser,20);

       
        #d: basic cycle time
        print 'Wait for new cycle...'
        time.sleep(2);

 

Weeeeeeell….I Hope the code is well commented,so it is not so unreadable! ūüėČ

As you see, I used a very nice Python library in order to connect to Google Mail using IMAP. You can download it from Segfault.in, and you can find good examples in the following pages: http://segfault.in/2010/07/playing-with-python-and-gmail-part-1/  and http://segfault.in/2010/08/playing-with-python-and-gmail-part-2/.

…Anyway, if you want to request some detail…I am here for you! ūüôā

And as usual, if you want to comment/modify/correct/redistribute/relink my code and my idea, you ‘re free to do it (…and thanks for the your pingbacks and for credits in your blogs etc. ūüėČ )!

Bye bye geek guys and girls… it’s time for me to go to install and test the system in my friend’s house (also if it’s too late for his vacation time..ah ah!!!)! ūüôā

 

Advertisements

The GL holidays post: a multifunction anti-intrusion system using Arduino!

….Thanks God it’s holidays time, my dear geeks! ūüôā

Well… during these days it isn’t a good idea to remain in a dark and little microelectronics laboratory, it’s a better solution to close your laboratory in order to go in a good house/hotel by the sea (or at the mountains)! ūüėČ

So, today Garretlabs wants to propose you a very simple (but) all-in one solution of house anti-intrusion alarm system, based on Arduino UNO.

This basic idea is very compact (but you can add all the subsystem you invent/want):

  • we will monitoring a set of house internal doors via a “wired connection”, using the digital in/out Arduino pins in order to detect a door openting (…I confess: this is inspired from a old joke which I knew ūüė¶ during a high school week at the mountains … as we say in Italy literally “White Week” that is “Settimana bianca”)
  • we will also manage a ultrasonic sensor in order to monitoring tha opening of another house internal door
  • in case of door opening detection, we will send an alarm email using the well known Arduino Ethernet Shield

This is, as usual, my hand-made design of the system ūüôā :

2014-07-22 09.17.29

The part list is (as usual) very short, because my goal is to use (when possible) only the parts present at the moment in my …garret! ūüėČ

  • One Arduino UNO
  • One Arduino Ethernet shield
  • One ultrasonic sensor
  • Assorted cables (never too many! ;-))

This is the Fritzing circuit (click on the image to see it bigger):

Ultrasonic alarm v1_bb

Well, remember to the Arduino place in a safe and fixed position in the house (for exaple fixed on the roof or fixed inside a electrical box on the wall) because if someone will open one of the doors connected by wire, our goal is that the wire will be disconnected from one of the two used Arduino digital pins (in order to open the controlled signal circuit).

Now, let’s go to code some line. The ultrasonic sensor is managed by the excellent newPing library, and the code to send email using the Arduino Ethernet shield is took from the my preferred site about Arduino resources (the only and one Arduino.cc …”made in Italy” rules! :-)), and modified by myself. The specific link is the following: http://playground.arduino.cc/Code/Email.

So, this is the code.

#include <SPI.h>
#include <Ethernet.h>
#include <NewPing.h>

#define TRIGGER_PIN 6 // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN 7 // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

// choose the MAC!
byte mac[] = { 0x90, 0xAA, 0xAA, 0x00, 0x59, 0x67 };

// change network settings
IPAddress ip( 192, 168, 0, 30 );
IPAddress gateway( 192, 168, 0, 1 );
IPAddress subnet( 255, 255, 255, 0 );

// change to your server SMTP
char server[] = "out.alice.it"; //note I used my italian ISP provider server to send emails... :-)
EthernetClient client;
unsigned int initial_distance;
bool ULTRASONIC_ALARM_ARMED=true;
bool PERIMETRAL_ALARM_ARMED=true;
void setup()
{   
   //start ehternet and serial
   Serial.begin(9600);
   pinMode(13,OUTPUT);
   digitalWrite(13,HIGH);
   Ethernet.begin(mac, ip, gateway, gateway, subnet);
   delay(2000);
   //calibration of ultrasonic sensor
   Serial.print("Calibrating....");
   for (int i=0;i<100;i++)
   {
      delay(50); // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
      unsigned int uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
      initial_distance+=uS / US_ROUNDTRIP_CM;
   }
   
   initial_distance=initial_distance/100;
   Serial.print("Initial distance:");
   Serial.print(initial_distance);
   Serial.println("cm");
   digitalWrite(13,LOW);
   //setup the perimetral cable
   pinMode(10,OUTPUT);
   pinMode(11,INPUT);
   digitalWrite(10,HIGH);
}

void loop()
{  
   byte inChar;
   unsigned int distance=0;
   unsigned int medium_distance=0;
   //inChar = Serial.read();
   for (int i=0;i<20;i++)
   {
      delay(50); // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
      unsigned int uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
      distance+=uS / US_ROUNDTRIP_CM;
   }
   medium_distance=distance/20;
   Serial.print (medium_distance);
   Serial.println (initial_distance);
   if(medium_distance<initial_distance && ULTRASONIC_ALARM_ARMED==true) //note that you can add a threshold in order to reduce false alarms
   { 
      if(sendEmail(1)) Serial.println(F("Email sent"));
      else Serial.println(F("Email failed"));
      ULTRASONIC_ALARM_ARMED=false; //the alarm should send only one email!
   }
   else
   {
      ULTRASONIC_ALARM_ARMED=true; //re-arm the alarm!
   }
   
   if(digitalRead(11)==LOW && PERIMETRAL_ALARM_ARMED)
   {
      if(sendEmail(2)) Serial.println(F("Email sent"));
      else Serial.println(F("Email failed"));
      PERIMETRAL_ALARM_ARMED=false; //the alarm should send only one email per violation!
   }
   else
   {
      //nothing to do: note that the perimetral wire alarm cannot be auto-rearmed! :-(
   }
}

//////////////////////////////////////////////////////////////////////////////
// Send email helper functions.
// Based on: http://playground.arduino.cc/Code/Email
//////////////////////////////////////////////////////////////////////////////

byte sendEmail(int type_alarm)
{
    byte thisByte = 0;
    byte respCode;
    if(client.connect(server,25)) {
        Serial.println(F("connected"));
    } else {
       Serial.println(F("connection failed"));
       return 0;
    }
    if(!eRcv()) return 0;
    Serial.println(F("Sending helo"));
    // change to your public ip
    client.println(F("helo 1.2.3.4"));
    if(!eRcv()) return 0;
    Serial.println(F("Sending From"));
    // change to your email address (sender)
    client.println(F("MAIL From: <your_email@alice.it>"));
    if(!eRcv()) return 0;

    // change to recipient address
    Serial.println(F("Sending To"));
    client.println(F("RCPT To: <your_recipient_address@your_provider.com>"));

    if(!eRcv()) return 0;
    Serial.println(F("Sending DATA"));
    client.println(F("DATA"));
    if(!eRcv()) return 0;
    Serial.println(F("Sending email"));
    // change to recipient address
   client.println(F("To: You <your_recipient_address@your_provider.com>"));

   // change to your address
   client.println(F("From: Me <your_email@alice.it>"));
   if(type_alarm==1) //ultrasonic alarm type
   {
      client.println(F("Subject: Arduino Ultrasonic alarm!\r\n"));
     client.println(F("The Arduino Ultrasonic alarm has been powered ON!"));
   }
   else if (type_alarm==2) //perimetral alarm type
   {
      client.println(F("Subject: Arduino Perimetral alarm!\r\n"));
      client.println(F("The Arduino Perimetral alarm has been powered ON!"));
   }
   client.println(F("."));
   if(!eRcv()) return 0;
   Serial.println(F("Sending QUIT"));
   client.println(F("QUIT"));
   if(!eRcv()) return 0;
   client.stop();
   Serial.println(F("disconnected"));
   return 1;
}

byte eRcv()
{
   byte respCode;
   byte thisByte;
   int loopCount = 0;
   while(!client.available()) {
      delay(1);
      loopCount++;
      // if nothing received for 10 seconds, timeout
      if(loopCount > 10000) {
          client.stop();
          Serial.println(F("\r\nTimeout"));
          return 0;
        }
     }
   respCode = client.peek();
   while(client.available())
   {
      thisByte = client.read();
      Serial.write(thisByte);
   }
   if(respCode >= '4')
   {
      efail();
      return 0;
   }
   return 1;
}

void efail()
{
   byte thisByte = 0;
   int loopCount = 0;
   client.println(F("QUIT"));
   while(!client.available()) {
      delay(1);
      loopCount++;
      // if nothing received for 10 seconds, timeout
      if(loopCount > 10000) {
          client.stop();
          Serial.println(F("\r\nTimeout"));
          return;
      }
   }

   while(client.available())
   {
      thisByte = client.read();
      Serial.write(thisByte);
   }
   client.stop();
   Serial.println(F("disconnected"));
}

Note that I used a SMTP email server which donesn’t use SSL access. In this case the SMTP sender library doesn’t work properly.

Well.. it’s very hot in Florence. I need a cold drink and a visit to a near pool. So, I think I will shutdown my computer now.;-)

But first…I hope you will have good vacation boys and girls. Your microelectronics worm should need a little time of relax, so see you soon at the end of August with other new open source ideas!