Domofon z powiadomieniem e-mailowym

Uruchomiłem już „internetowy domofon”. Za każdym razem gdy ktoś przy furtce wciśnie przycisk domofonu, dostaję powiadomienie e-mailowe z linkiem do zdjęcia drogiego gościa. Ponieważ w telefonie używam K-9 mail z obsługą IMAP IDLE, jako programu pocztowego, więc mail dociera do mnie w ciągu kilku sekund. Dzięki temu wiem, kiedy ktoś mnie odwiedza a nie ma mnie w domu. Gdy przyjdzie ktoś znajomy, mogę zadzwonić i powiedzieć, żeby poczekał, bo właśnie wracam do domu. Wkrótce będę mógł też otworzyć z telefonu furtkę i wpuścić gościa na posesję. Podobnie w przypadku śmieciarzy, gdy przyjadą odebrać śmieci, a furtka będzie zamknięta. Gdyby natmiast ktoś chciał się włamać, to istnieje pewne prawdopodobieństwo, że zadzwoni wpierw domofonem, żeby upewnić się, że nikogo nie ma w domu – wtedy jego zdjęcia trafią bezpiecznie na serwer internetowy poza domem.

Jak to działa? Opisywałem ostatnio integrację domofonu Eura ADP-05A7 z Arduino od strony sprzętowej. Teraz pora zająć się częścią programistyczną. Przy okazji powstaną zaczątki głównego modułu oprogramowania inteligentnego domu. Zgodnie z tym, co przedstawiałem wcześniej, Arduino Mega kontroluje różne zdarzenia (w tym wciśnięcie przycisku domofonu) i przez protokół http przekazuje informacje o nich do systemu nadrzędnego – routera z OpenWRT. Protokół komunikacyjny jest oczywiście już zdefiniowany. Jeżeli wydaje się to skomplikowane, to proponuję zajrzeć do wpisu o programowaniu systemu podlewania. Warto go przeczytać, choćby ze względu na to, że część oprogramowania po stronie Arduino będzie wspólna i jest tam szczegółowo opisana. Nie będziemy tworzyć wszystkiego od nowa, tylko dopiszemy dodatkowe funkcje.

Programowanie Arduino

Jak już wspomniałem, ta część będzie się opierać w dużej mierze na wcześniejszych pracach nad systemem nawadniania. Dla obsługi domofonu dołożyć trzeba odczytywanie i interpretację stanów wejść analogowych. Nie potrzebujemy dokładnych pomiarów napięcia, ineresuje nas tylko to, w jakim stanie jest domofon, czyli w jakim zakresie utrzymuje się napięcie na linii zasilającej bramofon. Dla przypomnienia – 0V oznacza wciśnięty przycisk bramofonu, trochę ponad 1V to stan oczekiwania (nic się nie dzieje), a około 12V oznacza podniesioną słuchawkę i rozmowę. Po drugiej stronie transoptora przekłada się to na odczyty wejścia analogowego Arduino na poziomie około 0, 300 i 1018. Jak widać wartości są odległe, więc jakiekolwiek zakłócenia nie powinny wpływać na prawidłową interpretację stanu (może poza momentami podnoszenia czy odkładania słuchawki, ale z tym łatwo sobie poradzić).

Wprowadźmy więc tablice dla wejść analogowych, analogicznie jak wcześniej było to z wejściami i wyjściami cyfrowymi. Dodatkowo, dla czytelności kodu, stała INTERCOM będzie oznaczać indeks tablicy odpowiadający domofonowi, a INTLEVEL1 i 2 będą określać granice mędzy stanami.

// tablica wejść analogowych
int analog_state[]={0,1,0,0,0,0,0};
int analog_pins[]={2,3,4,5,6,7,8,9};
unsigned long analog_lastchange[]={0,0,0,0,0,0,0};
#define ANALOG_SIZE 7 // liczba elementów w tablicy

#define INTERCOM 1
// numer wejścia analogowego (w tablicy) dla domofonu

#define INTLEVEL1 10
#define INTLEVEL2 700
#define INTDEBOUNCETIME 1000

Skąd wiedziałem, jakie są wartości odczytów dla różnych stanów domofonu? Przez zdefiniowanie komendy a (dla wyjaśnienia – protokół komunikacyjny) w funkcji runCommand (opisywanej we wpisie o programowaniu systemu nawadniania) odczytującej aktualny stan wejścia analogowego sprawiłem, że dało się to sprawdzić przez przeglądarkę internetową.

    case 'a': // pytanie o stan wejścia analogowego
      w=param.toInt();
      if( w<0 || w>ANALOG_SIZE ) return("Error");
      return( String(analogRead(analog_pins[w])) );
      break;

Mając te dane, pozostało napisanie prostej funkcji (i dodanie jej do głównej pętli – loop).

// sprawdza stan domofonu
void checkIntercom()
{
  int inp, state;

  inp=analogRead(analog_pins[INTERCOM]);

  state=1;
  if( inp<INTLEVEL1 ) state=0;
  if( inp>INTLEVEL2 ) state=2;

  if(state!=analog_state[INTERCOM] && analog_lastchange[INTERCOM]+INTDEBOUNCETIME>time )
  {
    analog_lastchange[INTERCOM]=time;
    analog_state[INTERCOM]=state;
    sendMessage('a', String(String(INTERCOM)+state) );
  }
}

Wyjaśnienia wymaga jeszcze zastosowanie stałej INTDEBOUNCE. Jest to zabezpiecznie przed odczytami „na granicy” – częstymi zmianami związanymi np. z niedoskonałością przycisków. Często przy przyciskaniu lub zwalnianiu przycisku następuje zjawisko bardzo krótkiego (na ułamki sekund) załączania i wyłączania styku. Ponieważ nas nie interesuje czy ktoś zwolnił przycisk sekundę wcześniej, czy później, czy wcisnął przycisk raz czy 5 razy w ciągu sekundy, więc dajemy sobie duży margines tolerancji, żeby wyeliminować tę niedoskonałość.

Gdy zmienia się stan, wysyłamy komunikat a (nie mylić ze znaczeniem a wysyłanego w drugą stronę) z nowym stanem wejścia. Np. gdy ktoś zadzwoni, to wyślemy xa10 (gdzie x to identyfikator Arduino), natomiast gdy rozmowa zostanie odebrana – xa12.

Jakie jest tu zagrożenie, które łatwo przeoczyć? Przekroczenie zakresu wewnętrznego zegara, ale tym chwilowo nie będziemy się przejmować, a wyeliminujemy to przy opisie sposobu radzenia sobie z tym problemem (wkrótce). Ponieważ po stronie Arduino to już wszystko, więc można przejść do OpenWRT.

Router z OpenWRT

Nie będę oczywiście opisywał jak wgrać OpenWRT do routera, bo temat jest zbyt szeroki i dobrze opisany w sieci. Poza tym tak naprawdę, czy ktoś używa routera z OpenWRT, Raspberry Pi czy komputera z Linuksem, nie ma tu znaczenia – wszystko będzie działać tak samo. Ograniczę się tylko do kilku słów dotyczących konfiguracji i programów wymaganych do działania (oczywiście, jak to w Linuksie, można spokojnie zastosować inne). OpenWRT działa na TP-Linku WR-1043ND z podłączonym zewnętrznym dyskiem USB (i exrootem). Zainstalowane jest też php na serwerze uhttpd. Do wysyłania poczty służy mstmp.

Zacznijmy od php. Skrypt cmd.php ma za zadanie odbierać polecenia z Arduino, zgodnie z protokołem komunikacyjnym. Jak na razie skrypt jedynie zapisuje wszystko do logu, a dodatkowo reaguje na komendę oznaczającą wciśnięcie przycisku dzwonienia w bramofonie.

<?php

$c=$_GET['c'];

exec("echo `date` ".$_SERVER['REQUEST_URI']." >> /tmp/arduino.log");

if( $c=="xa10" ) passthru( "/dom/domofon-event > /dev/null &" );

?>

Jak widać. w przypadku wykrycia dzwonienia (xa10 odebrane od Arduino), wywołujemy skrypt domofon-event. Jego wyjście przekierowujemy do /dev/null, bo chcemy od razu zakończyć połączenie z Arduino, a w przeciwnym wypadku skrypt php zakończyłby się dopiero po skończeniu działania przez domofon-event. I to na razie tyle działki php. Reszta to proste skrypty shellowe.

Za robienie zdjęć gościom odpowiada, opisywana wcześniej,  kamera Edimax IC-3030. Skonfigurowana jest w ten sposób, że po wywołaniu URLa /snapshot.jpg można pobrać aktualny widok z kamery. Skrypt powinien więc pobrać obraz, wysłać go na serwer www, powiadomić mnie mailem o fakcie odwiedzin z podaniem odnośnika do wykonanego zdjęcia. Potem jeszcze dla pewności zostaną zapisane 2 zdjęcia i również wysłane na serwer (już bez powiadomienia). Dodatkowo zagwarantujemy sobie, że gdy nie zakończyło się jeszcze robienie i wysyłanie zdjęć – nie robimy następnych (żeby zabezpieczyć się przed nadpobudliwym wciskaczem przycisku i wielokrotnym uruchamianiem skryptu). Program w ash shellu (prostszy bash) jest krótki:

#!/bin/ash

cd /mnt/kamera1_storage_path/

EVENT=/dom/event/domofon.txt
SURFIX=`date +%Y-%m-%d-%H-%M-%S`
URL=192.168.15.123/snapshot.jpg

if [ -e $EVENT ]
then
  exit
fi

touch $EVENT

wget $URL -O intercom-$SURFIX.jpg
/usr/skrypty/mailsend "Dzwonek do domofonu http://www.mojserwer.www/sciezka/intercom-$SURFIX.jpg"
/dom/sendwebcam
sleep 1
SURFIX=`date +%Y-%m-%d-%H-%M-%S`
wget $URL -O intercom-$SURFIX.jpg
sleep 5
SURFIX=`date +%Y-%m-%d-%H-%M-%S`
wget $URL -O intercom-$SURFIX.jpg
/dom/sendwebcam

rm $EVENT

W pierwszej kolejności sprawdzamy, czy plik zapisany jako $EVENT istnieje (czyli inna instancja skryptu się wykonuje). Jeżeli tak, kończymy działanie. Jeżeli nie – tworzymy go. Potem ściągamy z kamery obraz i zapisuemy pod określoną nazwą. Następnie wysyłamy powiadomienie z linkiem do zdjęcia (nie ma go jeszcze na serwerze) i uruchamiamy skrypt wysyłający oczekujące zdjęcia (w tym wypadku jedno) na zewnętrzny serwer www. Po odczekaniu sekundy, a potem pięciu robimy kolejne zdjęcia i znowu wysyłamy (bez powiadomienia). Na koniec pozostaje uzunąć plik blokujące wielokrotne uruchomienie. Co jeżeli wyłączymy prąd podczas wykonywania skryptu (szansa minimalna, ale zawsze)? Zostanie plik z lockiem i całość przestanie działać. Ale bez strachu – przy starcie i co jakiś czas uruchamiam skrypt czyszczący stare blokady – na wszelki wypadek.

Co zawiera sendwebcam?

#!/bin/sh
DIR=/mnt/kamera1_storage_path
mv $DIR/*.jpg $DIR/m/
scp -i /root/.ssh/id_rsa $DIR/m/*.jpg webcam@mojswerwerwww:~
[ $? -eq 0 ] && {
rm $DIR/m/*.jpg
}

Nic skomplikowanego – przenosi wszystkie pliki zdjęć do podkatalogu i wysyła je przez scp. Gdy operacja się uda – usuwa dane z podkatalogu. Gdybyśmy chcieli wykonywać skrypty wysyłające równolegle (a nie chcemy, bo zabezpieczyliśmy się wcześniej), to należałby generować nazwę podkatalogu i go tworzyć przy każdym wykonaniu.

Do wyjaśnienia pozostał jeszcze banalny skrypt mailsend, wysyłający przez program mstmp e-maila z treścią przyjętą jako parametry.

NOW=`date -u +%s`
PLIK=/tmp/mail$NOW.txt

echo "From: d@mojserwer.pocztowy" > $PLIK
echo "To: ja@mojserwer.pocztowy" >> $PLIK
echo "Subject: Dom" >> $PLIK
echo "" >> $PLIK
echo $1 >> $PLIK

/usr/bin/msmtp -t < $PLIK
rm $PLIK

I to już wszystko. Działa jak zaprojektowano. Jak widać, nie było ciężko. To świetnie obrazuje, że posiadanie już podstaw instalacji inteligentnego domu sprawia, że dokładanie kolejnych funkcji jest bardzo proste.

Advertisements

11 Responses to Domofon z powiadomieniem e-mailowym

  1. Adrian says:

    Czy nie zastanawiałeś się nad wykorzystaniem do tego Raspberry Pi + Dedykowana kamera?

    Ta kamera ma 5 MPx z skryptem do wykrywania ruchu powinna lepiej działać niż ten edimax (ma problemy z wykrywaniem ruchu albo wykrywa za dużo albo za mało)

    Można by było zrobić fajny monitoring z tego a dodatkowo za pomocą GPIO wykrywać napięcie na domofonie + wysyłać fotki. O pomiarze temperatury przy pomocy 1-wire z GPIO już nie wspomnę 🙂

    • techniczny says:

      Myślałem, nawet miałem kamerę usb podłączoną do routera, ale kamery IP są praktyczniejsze. Kabel ethernet może być długi. Jak nie da się pociągnąć, to może być wifi. Łatwo zainstalować w dowolnym miejscu.

      • Adrian says:

        ale ta kamera na RPI nie jest na usb tylko na takiej tasiemce podpieta do specjalnie przewidzianego zlacza. Tez sie bawilem kamerkami podpietymi do routera ale to jest dobre tylko na chwile sie pobawic. Do Rpi mozesz podpiac wifi i tez masz wmdowolnym miejscu. A konfiguracja takiej kamery nie ograniczona.

      • techniczny says:

        A, rozumiem, RPi jako kamera IP z dodatkami. Jeżeli faktycznie ta kamerka ma 5Mpix, właściwy kąt widzenia i radzi sobie z silnym światłem i w ciemności, to mogłoby to być niezłe rozwiązanie. Przynajmniej jedna taka mogłaby być.

  2. Adrian says:

    Znalazłem skrypt w phytonie do detekcji ruchu na tej kamerce
    http://www.raspberrypi.org/phpBB3/viewtopic.php?f=43&t=45235&p=358428#p358428
    O samej kamerce możesz poczytać:
    http://picoboard.pl/kamera-dla-raspberry-pi-recenzja/

    • techniczny says:

      Wygląda ciekawie, choć szumy dość duże. Nie mogę znaleźć informacji o kącie widzenia.

      • techniczny says:

        Wszyscy mają RPi, to kupiłem zabawkę i ja 🙂 Wkrótce pewnie pojawi się jakiś test kamery…

      • Adrian says:

        No to fajnie, że Cie przekonałem.
        Zakupiłeś już też kamerkę czy dopiero będziesz zamawiał?
        Ja moją będę miał w tym tygodniu 😉
        Szkoda, że RPI nie ma wyprowadzonych wejść analogowych, ale jest rozszerzenie które to umożliwia 🙂

      • techniczny says:

        Mój domofon zarejestrował wizytę listonosza, ale nikogo nie było. Jak dobrze pójdzie, dzisiaj odbiorę paczkę z poczty.

  3. Adrian says:

    Opanowałeś tą kamerę do rpi?

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: