Projektityö: Varashälytin Arduinolla


Prototyypin rakentaminen-kurssin projektityönä minulla on varashälytin, joka liikkeen sekä oven avaamisen. Se kerää lokitiedostoa tietokoneelle ja lähettää hälytyksen sähköpostitse jos hälytys laukeaa.

Laitteeseen on kytketty näyttö, josta näkee tilatietoa varashälyttimen tilasta. Laitteessa on myös RFID-lukija, jolla varashälytin aktivoidaan ja deaktivoidaan.

varashalytin2-bb

varashalytin-hardware 

Arduinon pinnijärjestys

Arduinon lähes kaikki digitaalipinnit ovat käytössä. Seuraavassa lista pinneistä ja mihin ne on kytketty:

Arduinon ohjelmalogiikka

Varashälyttimen toimintalogiikka Arduinossa on jaettu SchedulerARMAVR-kirjaston avulla kahteen looppiin, joista toinen tarkkailee RFID-lukijaa sekä ohjaa näyttöä, ja toinen joka tarkkailee magneettisensoria ja liiketunnistinta.

Varashälytin tarkkailee jatkuvasti sensoreita, mutta ilmoittaa tapahtumista näytöllä vain “Armed”-tilassa. Sarjaportin yli tapahtumia kirjataan jatkuvasti hälytyksen tilasta riippumatta.

Kun hälytin kytketään “Armed”-tilaan, Arduino tarkkailee magneettikytkintä ja liiketunnistinta. Jos liikettä havaitaan tai ovi on auki yli 10 sekunnin ajan(toteutuksessa on havaittu ettei aikaraja aina täsmää jos liike ei ole jatkuvaa), hälytys laukeaa joka ilmoitetaan äänimerkillä, näytöllä sekä sarjaportin ylitse tietokoneelle. Aikaraja näkyy näytön oikeassa yläreunassa. Hälytyksen voi kuitata vain oikealla RFID-tunnisteella tai painamalla Arduinon Reset-painiketta.

varashalytin-naytto

varashalytin_final.ino:

#include <SchedulerARMAVR.h>
#include <LiquidCrystal.h>
#include <OneWire.h>

/*
  Varashälytinohjelma, joka tarkkailee liikettä
  ja magneettikytkintä ja hälyttää niistä sarja-
  portin ylitse.

  Author: Joni Junni
  Date: 13.5.2013

*/

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int redLedPin = 13;
int greenLed = 6;
int pirPin = 8;
int magnetPin = 7;
int pirState = LOW;
int val = 0;
int i = 0;
int sensorPin = 0;
int alertTimer = 10;
boolean armedState;
boolean magnetState;
boolean motionState;
boolean alarmConfirmed;

// Piezo stuff
int  NOTEg = 2550;    // 392 Hz 
int  NOTEC = 1912;    // 523 Hz 
int  NOTER = 0;      // REST-note
int speakerOut = 9;
int melody[] = {NOTEC, NOTEg};
int beats[]  = {16, 16}; 
int MAX_COUNT = sizeof(melody) / 2; // Melody length, for looping.
long tempo = 10000;
int rest_count = 100;
int tone_ = 0;
int beat = 0;
long duration  = 0;
void playTone() {
  long elapsed_time = 0;
  if (tone_ > 0) { // if this isn't a Rest beat, while the tone has 
    //  played less long than 'duration', pulse speaker HIGH and LOW
    while (elapsed_time < duration) {
      digitalWrite(speakerOut,HIGH);
      delayMicroseconds(tone_ / 2);
      digitalWrite(speakerOut, LOW);
      delayMicroseconds(tone_ / 2);
      elapsed_time += (tone_);
    } 
  }
  else { // Rest beat; loop times delay
    for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count
      delayMicroseconds(duration);  
    }                                
  }                                 
}

// RFID Stuff
OneWire ds(10); // iButton reader pin
byte addr[8]; // Data read from the iButton reader
int but[6] = {
  195,133,181,20,0,0}; // Allowed RFID-authentication button
String keyStatus="";

void setup() {
  Serial.begin(9600);
  pinMode(redLedPin, OUTPUT);
  pinMode(greenLed, OUTPUT);
  pinMode(magnetPin, INPUT);
  pinMode(pirPin, INPUT);
  pinMode(speakerOut, OUTPUT); 
  lcd.begin(16, 2);
  armedState = false;    // Used for setting system state
  motionState = false;  // Used for detecting motion state
  magnetState = false;  // Used for detecting door state
  alarmConfirmed = false;  // Used to reset triggered alarm
  Scheduler.startLoop(loop2);  // Starts additional loop function
}

void loop() {
  // Check for auth key button
  getKeyCode();
  if(armedState == false) {
    lcd.clear();
    lcd.print("SYSTEM:DISARMED");
  } 
  else {
    if(alertTimer == 0 && alarmConfirmed == false) {
      lcd.clear();
      lcd.print("ALARM TRIGGERED!");
      lcd.setCursor(0, 1);
      lcd.print("EMAIL SENT!");
    } else {
    lcd.clear();
    lcd.print("SYSTEM:ARMED  ");
    lcd.print(alertTimer);
    if(motionState == true) {
      lcd.setCursor(0, 1);
      lcd.print("MOTION DETECTED");
    }
    if(magnetState == true) {
      lcd.setCursor(0, 1);
      lcd.print("                ");
      lcd.setCursor(0, 1);
      lcd.print("DOOR OPEN");
    }
    }
  }

  Scheduler.delay(1000); // Poll every second
}
void loop2() {
  checkPirSensor();
  checkMagnetSensor();
  Scheduler.delay(1000);
  yield();
}

void checkPirSensor() {

  val = digitalRead(pirPin);

  // Motion detected
  if (val == HIGH) {
    motionState = true;
    if(armedState == true) {    
      if(alertTimer > 0) {
        alertTimer = alertTimer - 1;
      } 
      else {
        Serial.println("ALARM");
        // Trigger alarm
        triggerAlarm();
      }
      blinkAlertLed();
    }
    Serial.println("P");
  } 
  else {
    // Send a signal to serial port that motion has stopped
    if(motionState == true) {
      Serial.println("PS");
    }
    motionState = false;
  }
}

void checkMagnetSensor() {

  val = digitalRead(magnetPin);

  // Magnet switch is open (Door is open)
  if (val == HIGH) {
    //lcd.clear();
    lcd.setCursor(0, 1);
    magnetState = true;
    if(armedState == true) {    
      if(alertTimer > 0) {
        alertTimer = alertTimer - 1;
      } 
      else {
        // Trigger alarm
        Serial.println("ALARM");
        triggerAlarm();

      }
      blinkAlertLed();
    }
    Serial.println("M");
  } 
  else {
    // Send a signal to serial port that door has been closed
    if(magnetState == true) {
      Serial.println("MS");
    }
    magnetState = false;

  }
}
void checkLogin() {
  // If key button format is ok
  if(keyStatus=="ok"){
    /*
  byte i;
     for( i = 5; i >0; i--) {
     Serial.print(" : ");
     Serial.print(addr[i], DEC);
     }
     Serial.println();
     */
    // Check if user is authorized
    if(addr[1] == but[0] && addr[2] == but[1] && addr[3] == but[2] && addr[4] == but[3]) {
      FlashGreenLed();
      Serial.println("AU");
      if(armedState == false) {
        setArmed();
      } 
      else {
        setDisarmed();
      }
      if(alarmConfirmed == false && alertTimer == 0) {
        alarmConfirmed = true;
      }
    } 
    else {
      Serial.println("UU");
      blinkAlertLed();
    }
  }

}

void getKeyCode(){
  keyStatus="";

  // If there is nothing to read, stop and return
  if ( !ds.search(addr)) {
    ds.reset_search();
    return;
  }
  keyStatus="ok";
  checkLogin();
  ds.reset();
}

void blinkAlertLed() {
  // Blink the alert led
  for(i=0;i<5;i++) {
    digitalWrite(redLedPin, HIGH);
    Scheduler.delay(200);
    digitalWrite(redLedPin, LOW);
    Scheduler.delay(200);
  } 
}
void FlashGreenLed() {
  digitalWrite(greenLed, HIGH);
  Scheduler.delay(1500);
  digitalWrite(greenLed, LOW);
}
void setArmed() {
  Serial.println("A");
  armedState = true;
  alertTimer = 10; // Reset alert timer
  alarmConfirmed = false;
}
void setDisarmed() {
  Serial.println("D");
  armedState = false; 
}
void triggerAlarm() {
  // Set up a counter to pull from melody[] and beats[]
  while(alarmConfirmed == false) {
    for (int i=0; i<MAX_COUNT; i++) {
      tone_ = melody[i];
      beat = beats[i];
      duration = beat * tempo; // Set up timing
      playTone(); 
      Scheduler.delay(1);    
    }
  }
}

Sarjaporttikommunikaatio tietokoneen kanssa

Varashälytin kommunikoi tietokoneen kanssa sarjaportin yli lokitiedoston kirjoittamista sekä varoitusviestin lähettämistä varten. Määritin varashälyttimen koodiin seuraavanlaiset komennot jota se voi tietokoneelle lähettää:

Tietokoneen päässä on python-ohjelma kuuntelemassa sarjaporttia ja se suorittaa komentoja sen mukaan mitä varashälytin sille syöttää. Ohjelma hoitaa tapahtumien kirjoittamisen lokiin sekä sähköpostivaroituksen lähettämisen jos hälytys laukeaa.

varashalytin.py:

#!/usr/bin/env python
#coding: utf8
# Ohjelma joka kuuntelee varashälyttimen sarjaporttia
# ja suorittaa komentoja sen mukaan mitä hälytin käskee
# Author: Joni Junni
# Date: 13.5.2013

import serial
import time
import smtplib

ser1 = serial.Serial("/dev/tty.usbmodem1421")

while True:
    try:
        line = ser1.readline()
        #print(line)
        timestamp = time.asctime( time.localtime(time.time()) )

        if line.find('ALARM') !=-1:
            message =  timestamp + ': ALARM WAS TRIGGERED'

            sender = 'arduinohalytys@gmail.com'
            receivers = ['joni.junni@me.com']

            emailmsg = """From: Arduino Alert <arduinohalytys@gmail.com>
To: Joni Junni <joni.junni@me.com>
Subject: Alert was triggered!

Your home might be intruded.
                        """

            try:
                smtpObj = smtplib.SMTP('smtp.gmail.com', 587)
                smtpObj.ehlo()
                smtpObj.starttls()
                smtpObj.ehlo()
                smtpObj.login('arduinohalytys','Trolololo')
                smtpObj.sendmail(sender, receivers, emailmsg)         
                print "Successfully sent email"
            except smtplib.SMTPException:
                print "Error: unable to send email"

        elif line.find('UU') !=-1:
            message = timestamp + ': Unauthorized user tried to log in'
        elif line.find('AU') !=-1:
            message = timestamp + ': Authorized user has been logged'
        elif line.find('MS') !=-1:
            message = timestamp + ': Door was Closed'
        elif line.find('PS') !=-1:
            message = timestamp + ': Motion stopped'
        elif line.find('P') !=-1:
            message = timestamp + ': Motion detected'
        elif line.find('M') !=-1:
            message = timestamp + ': Door Open'
        elif line.find('A') !=-1:
            message = timestamp + ': System Armed'
        elif line.find('D') !=-1:
            message = timestamp + ': System Disarmed'

        f = open('alertLog.log','a')
        f.write(message + '\n')
        print message
    except (serial.serialutil.SerialException, OSError):
        pass

Lähteet