Alarma domestica amb Raspberry Pi

Com alguns sabreu, arrel del primer confinament vaig decidir comprar-me una Raspberry Pi 4 per fer els meus experiments (aprendre en funcionament de Docker i acostumar-me a utilitzar linux des de la shell).

Tot i que hi ha un “fotimer” de projectes molt interessants per la Raspberry, la veritat és que m’ha costat trobar un projecte que tingues una utilitat per a mi (a banda del servidor DNS i VPN que comento en aquest article).

Arrel d’una conversa familiar va sorgir la idea de fer una alarma. Es poden implementar diferents tipus d’alarma:

  • Detecció per sensor de moviment: Es basa en connectar un sensor de moviment als port de la Raspberry. El funcionament és molt senzill i assequible econòmicament.
  • Detecció a través de l’imatge d’una càmera: Tot que és molt interessant a nivell acadèmic i et pots descarregar les solucions d’altre gent, es força més complicat tècnicament i no afegeix funcionalitat (inclús la redueix, ho explicaré més endavant).
  • Detecció pe raig laser: També es molt senzill de fer però es menys versàtil ja que requereix que posis un reflectant a l’altre punta de la paret i si vols un radi “d’acció” tan gran com el del detector de moviment hauries de posar dos emissors laser amb dos reflectants.

Com em considero una persona pragmàtica, em vaig decantar pel primer tipus.

Objectiu

Implementar una alarma que sigui capaç d’enviar-me una notificació al meu dispositiu mòbil quan detecti un intrús. Per tal d’assegurar-me que no es tracta d’un fals positiu o d’un accés autoritzat (família) haurà de permetre que pugui veure qui a accedit al meu domicili (preferiblement en temps real). A demés, aprofitant que ja tinc implementat el servidor VPN, podrem gestionar la alarma des de qualsevol lloc amb “total” seguretat.

Material necessari

El mínim que necessitem es:

  • Raspberry Pi amb ports GPIO i connectada a internet (en el meu cas Raspberry Pi 4 B)
  • Càmera per a la Raspberry. Recomano que tingui les següents característiques:
    • Objectiu gran angular o ull de peix (quants més graus de FOV millor. La meva és de 175º)
    • Visió nocturna (dos focus de llum infraroja)
    • Que encengui i apagui la llum infraroja depenent de la llum ambient.
    • Que es pugui ajustar l’enfoc de la càmera, ni que sigui manualment
  • Un reproductor d’streaming: Jo he fet servir el VLC que es molt lleuger i te aplicació per per qualsevol sistema operatiu i per dispositius mòbils.
  • Un sensor de moviment per a Raspberry: Com són econòmics recomano comprar 2 o 3 ja que és bastant fàcil que algun falli). Aquest és el que vaig fer servir.
  • Cables per connectar el sensor: Realment només són necessaris 3 fils que siguin femella per les dues puntes però si no teniu cables, podeu comprar un pack com aquest i oblidar-vos per molt de temps.
  • (opcional) Un client SSH: En el meu cas faig servir el RaspController que és una petita meravella per gestionar remotament la Raspberry.

Us deixo l’enllaç d’un parell de models de la càmera, model 1, model 2 ja que costa de trobar una que compleixi amb tots els requisits.

Configuració prèvia necessària

La Raspberry hauria de tenir una mínima de configuració prèvia.

Electrònica

Ara que ja sabem tot el necessari per seguir aquest article, anem per feina .

Sensor de moviment

Com podeu veure en l’imatge, el sensor de moviment té les següents parts:

  • Pin VCC (alimentació de 5v)
  • Pin OUT (el que envia la senyal si s’ha detectat un moviment)
  • Pin GND (terra)
  • Delay Time adjust: Potenciòmetre per regular el temps que passa entre que detecta el moviment i envia la senyal.
  • Distance adjust: Potenciòmetre per regular la sensibilitat
Esquema de connexions

Us recomano que els dos potenciòmetres estiguin al màxim cap a l’esquerra (mirant el detector tal com està en l’imatge) que sigui possible, de manera que tant el “delay” com la sensibilitat estiguin al mínim.

Per altre banda tenim l’esquema de pins de la Raspberry 4 (també és vàlid per la 3 i la zero)

Esquema de PINS de Raspberry 4, 3 i Zero

Ara ja tenim tota la informació per poder connectar el sensor a la Raspberry. Com veureu en la imatge he fet servir un allargador (mascle-femella) per poder buscar una millor posició pel sensor. Com el sensor és força potent enviarà la senyal si detecta un moviment en el 80% de la superfície del menjador (uns 3m quadrats)

Connexió del sensor de moviment

La “traducció” a la vida real de la connexió a la placa, el teniu en la següent imatge, on es pot veure que finalment vaig utilitzar el pin 2 per VCC (cable negre), el pin 9 per la terra (cable gris) i el 15 per la sortida (cable blanc). Els altres dos cables vermells són dels ventiladors de la Raspberry, no en feu cas)

Connexió sensor de moviment als pins de la Raspberry

En quant a hardware ja hem fet el més difícil. Ja veureu que la connexió de la càmera és força més simple.

Càmera

Com us comentava en els requisits, en el meu cas em vaig decantar per una càmera amb molt FOV (field of view). Pels que no estigueu familiaritzats això vol dir que te una òptica gran angular per a que es vegi la màxima superfície possible de manera que no hagi de posar més càmeres per cobrir tot el menjador. Distorsiona una mica l’imatge però no estem buscant fer una foto de retrat, sinó veure que esta passant en aquella àrea. També es important que l’enfoc sigui ajustable, del contrari seria possible que vegis l’intrús però sigui impossible d’identificar ja que no es veurà amb suficient nitidesa.

Òbviament és molt important que sigui capaç de fer fotografies o vídeos nocturns, per tant la càmera ha d’incorporar llum infraroja. Per tal d’estalviar energia i temperatura vaig buscar una que portés un sensor de llum de manera que graduí automàticament la intensitat de la llum infraroja que emet en funció de la llum ambiental.

A diferencia del detector de moviment, la Raspberry Pi te un port especific per connectar una càmera

Com veieu en les fotografies no queda gaire dissimulat però us puc assegurar que es totalment funcional i molt practic. Amb una impressora 3D es podria fer quelcom més per tal de fixar i amagar millor tant el sensor com la càmera.

No queda gaire amagat, però es totalment funcional

Finalment el vídeo queda como es veu en la següent imatge. Com podeu observar en el meu cas es molt útil que tingui 175 graus de FOV ja que es veu tot el menjador incloent la sortida a la terrassa. La idea es que l’angle coincideixi amb tot el camp d’actuació del sensor de moviment. Com veureu més endavant en l’apartat de programació, no estic gravant ni a FullHD que es el màxim que permet la càmera. Cal tenir en compte que nos nomes voleu gravar un vídeo sinó que en voleu fer streaming. Per tal de no saturar cap de les dues coses, vaig escollir no utilitzar la màxima resolució de la càmera. En aquest sentit s’ha de dir que tot i aixo s’experimenten certs talls en el streaming, per tant si volguéssiu un flux sense interrupcions hauríeu de baixar encara més la qualitat del vídeo. De totes maneres aquests petits talls nomes es veuen en el streaming, en canvi el vídeo que es guarda en la SD es totalment fluït.

La superfície que es veu, amb la data i l'hora incrustada en el vídeo
Així es veu a plena foscor

Programació

Un cop tingueu tot el hardware instal·lat, ja podeu començar a pensar quina funcionalitat voleu que tingui. La idea bàsica és que quan el sensor de moviment detecti que ha passat quelcom per davant activi el pin 15 i el script de Python ens envií una notificació al mòbil mitjançant el xat de Telegram que teniu amb el bot.

Creació d’un bot en Telegram

El primer pas serà crear el bot de Telegram que ens permeti comunicar-nos amb la Raspberry. Per crear el bot recomano fer-ho mitjançant el BotFather.

Primer haureu d’entrar al Telegram i buscar ” BotFather”. Segons entreu a xat de BotFather ens mostrarà una ajuda de les diferents funcionalitats de la mateixa. En resum, podeu crear ordres, posar-li una imatge al vostre bot, una descripció, crear jocs, … Lo important és crear el bot i obtindre un “token” del bot, que és el que us ha d’identificar com a desenvolupadors del bot i que haureu de mantenir en secret si no voleu que qualsevol altra persona pugui modificar el comportament del vostre bot. Per crear el bot, teclejareu /start i després /newbot

El bot us contestarà automàticament preguntant pel nom del bot a crear. Posareu el nom que desitgeu.

A continuació, us tornarà a demanar un nom però que acabi en _bot. Posareu el mateix nom anterior però acabat en _bot. Després d’això ens apareixerà el “token” en pantalla i un enllaç al vostre bot. Òbviament el token l’haureu de guardar de manera que no sigui visible ja que qualsevol podria utilitzar el token per fer servir el vostre bot.

També necessitareu saber el nostre Chat ID. Per fer-ho, la manera més senzilla és, tenint el client d’escriptori de Telegram instal·lat en l’ordinador i fer clic en aquest enllaç. Com veieu no és més que un bot fet per una altre persona que es limita a donar-te el teu chat id i el teu pseudònim.

Amb aquest dos paràmetres ja podeu muntar l’estructura bàsica del vostre programa en Python.

Script de Python

Anem a revisar com vaig fer el script. Ho explico per parts però no us preocupeu que més endavant deixo tot el script en un sol bloc perquè pugueu fer “copy&paste”.

Primer vaig importar les llibreries necessàries

import RPi.GPIO as GPIO
import time
import telepot
import datetime

Com veieu la primera ens permetrà llegir i escriure els estats dels diferents PINS (GPIO) de la Raspberry

La llibreria “time“, per poder cridar les funcions que us permetran fer una pausa en l’execució del programa, la llibreria “telepot” que us permetrà comunicar-vos amb el bot de Telegram i per ultim la “datetime” per poder treballar amb dates i hores

GPIO.setmode(GPIO.BOARD)
GPIO.setup(15,GPIO.IN)
estat_anterior = False
estat_actual = False
time.sleep(4)
token = 'token_bot_telegram'
xat_id = 'identificador_bot_telegram'

La llibreria GPIO és molt fàcil d’utilitzar però heu de saber que hi ha dues maneres de fer referencia als pins. La primera és pel nom (per exemple “GPIO25”, “GND”…) del i l’altre pel numero del pin (1, 2, 3…). La manera en la que us referireu als pins és configura mitjançant la instrucció GPIO.setmode. Si ens volem referir als pins pel seu nom escriureu GPIO.setmode(GPIO.BCM)  si pel contrari preferiu fer-ho pel numero escriureu GPIO.setmode(GPIO.BOARD).

Per saber tant el nom com els números haureu buscar-los a l’esquema de pins en la documentació de la vostra Raspberry però per la Raspberry 3,4 i Zero és pot veure en la segona imatge d’aquest post

En la segona línia, inicio el valor del pin 15 a “IN” (1). També inicialitzo dos variables que em serviran per portar un control de l’estat de l’alarma i saber quan canvia i dues variables on posaré, per una banda el valor del token que m’ha de servir per comunicar amb el bot de Telegram i per l’altre l’identificador del xat on envio els missatges.

try:
        while True:
                time.sleep(1)
                estat_previ = estat_actual
                estat_actual = GPIO.input(15)
                if estat_actual != estat_previ:
                        if estat_actual == True:
                                nou_estat = "ALARMA ON"
                                bot = telepot.Bot(token)
                                bot.sendMessage(xat_id, 'POSSIBLE INTRUS')
                        else:
                                nou_estat = "ALARMA OFF"

                        print(nou_estat + " " + time.strftime("%d-%m-%y a les %H:%M:%S"))
                        time.sleep(1)

El nucli del programa és molt senzill. Es basa crear un bucle infinit i en cada iteració compara l’estat previ del pin on el sensor de moviment ens marca si ha detectat moviment (en aquest cas el pin 15) amb l’estat actual. Si ha canviat d’estat i el pin esta activat vol dir que ha detectat moviment. En aquest cas, escric la frase “POSSIBLE INTRÚS” en el xat de Telegram que tinc amb el bot que hem creat prèviament. Per contra, si ha canviat d’estat però el pin esta inactiu, vol dir que l’alarma esta en “repòs”. Com veureu cada vegada que canvia d’estat escric la data i hora per la sortida estàndard. Això nomes es per “debuggar”. Es important respectar els time.sleep(1) ja que sinó el programa es saturaria (recordeu estem dins d’un bucle infinit)

Per últim, per poder sortir controladament de la aplicació he afegit les següents línies.

except KeyboardInterrupt: # Ctrl+c
    pass
finally:
        GPIO.cleanup()
        print("Programa finalitzat")

Pitjant Crt+c tanco el programa, netejant els estats dels pins i mostrant un missatge conforme s’ha tancat correctament.

Us deixo tot el codi aquí sota

import RPi.GPIO as GPIO
import time
import telepot
import datetime
GPIO.setmode(GPIO.BOARD)
GPIO.setup(15,GPIO.IN)
estat_anterior = False
estat_actual = False
proces = 0
time.sleep(4)
token = 'token_bot_telegram'
xat_id = 'identificador_bot_telegram'

try:
        while True:
                time.sleep(1)
                estat_previ = estat_actual
                estat_actual = GPIO.input(15)
                if estat_actual != estat_previ:
                        if estat_actual == True:
                                nou_estat = "ALARMA ON"
                                bot = telepot.Bot(token)
                                bot.sendMessage(xat_id, 'POSSIBLE INTRUS')
                        else:
                                nou_estat = "ALARMA OFF"

                        print(nou_estat + " " + time.strftime("%d-%m-%y a les %H:%M:%S"))
                        time.sleep(1)

except KeyboardInterrupt: # Ctrl+c
    pass
finally:
        GPIO.cleanup()
        print("Programa finalitzat")

Amb això ja funcionaria l’alarma, però no podríeu veure qui esta dins de casa i en aquest punt entra la càmera.

Una possible solució es fer una foto en el moment que detecteu el moviment, per exemple just abans d’enviar el missatge al xat de Telegram. Per fer-ho hauríeu d’afegir les següents línies:

import subprocess
nom_foto = "cam_"+time.strftime("%d-%m-%y_%H:%M:%S")+".jpg"
subprocess.call(['raspistill', '-w', '1280', '-h', '960','-vf', '-o', 'fotos/' + nombre_foto , '-ex', 'sports'])
foto = open('fotos/' + nombre_foto, 'rb')
bot.sendPhoto('209075756', foto)

En la primera línia importeu la llibreria “subprocess” que us permetrà l’execució d’un altre programa dins del programa principal com es veu en la línia 3, on es crida al programa que ve per defecte en la Raspberry per fer una foto amb la càmera. En aquest enllaç podreu veure per a que serveixen tots els paràmetres que li he especificat, però per resumir li estareu indicant que voleu una foto de 1280×960, que la voleu girar verticalment, el nom i la ruta del fitxer de la foto i que el mode d’exposició es “sport” de manera que voleu que dispari ràpid amb el temps d’exposició al mínim. En les línies 4 i 5 utilitzareu el bot per enviar la fotografia al xat del Telegram.

Finalment el codi quedaria així:

import RPi.GPIO as GPIO
import time
import telepot
import datetime
import subprocess
GPIO.setmode(GPIO.BOARD)
GPIO.setup(15,GPIO.IN)
estat_anterior = False
estat_actual = False
proces = 0
time.sleep(4)
token = 'token_bot_telegram'
xat_id = 'identificador_bot_telegram'

try:
        while True:
                time.sleep(1)
                estat_previ = estat_actual
                estat_actual = GPIO.input(15)
                if estat_actual != estat_previ:
                        if estat_actual == True:
                                nou_estat = "ALARMA ON"
                                nom_foto = "cam_"+time.strftime("%d-%m-%y_%H:%M:%S")+".jpg"
                                subprocess.call(['raspistill', '-w', '1280', '-h', '960','-vf', '-o', 'fotos/' + nombre_foto , '-ex', 'sports'])
                                foto = open('fotos/' + nombre_foto, 'rb')
                                bot = telepot.Bot(token)
                                bot.sendMessage(xat_id, 'POSSIBLE INTRUS')
                                bot.sendPhoto(xat_id, foto)
                        else:
                                nou_estat = "ALARMA OFF"

                        print(nou_estat + " " + time.strftime("%d-%m-%y a les %H:%M:%S"))
                        time.sleep(1)

except KeyboardInterrupt: # Ctrl+c
    pass
finally:
        GPIO.cleanup()
        print("Programa finalitzat")

Aquest mètode té el problema que tot i configurar-li l’exposició “sport” per quan la Raspberry llença la foto, l’intrús ha pogut sortir del camp de visó de la càmera o aparèixer només una part d’ell. A demés com el script estarà fent servir la càmera per fer la foto, ens bloqueja l’us de la càmera per a qualsevol altre programa, de manera que al final vaig decidir prescindir de la foto i crear un altre script que em permetes gravar el que està passant i emetre-ho en streaming.

Streaming

Per tenir un grau major de llibertat vaig decidir no controlar des de el programa de la alarma l’inici i el fi de la gravació/emissió. Em resultava difícil decidir quan s’hauria de para la gravació i per tant, crec que la millor decisió es iniciar-lo manualment des de el mòbil. Com es pot veure en l’imatge des de la mateixa aplicació de RaspController (la que em permet connectar a la Raspberry des de qualsevol lloc) també dona la possibilitat de guardar instruccions en accessos directes per tal de no tenir que escriure-les cada cop

Així doncs em vaig crear un parell de fitxers .sh per encendre i apagar el servei de streaming.

Per fer l’streaming vaig fer servir l’aplicació per defecte de la Raspberry (raspivid) per gestionar la càmera i concateno la sortida d’aquest a un altre programa, el tee. Aquest programa em permet guardar el vídeo a la SD de la Raspberry i al mateix temps treure’l per una altre “sortida”. A aquesta segona sortida hi ha el Netcat que em permet emetre en streaming el vídeo que estic gravant, de manera que qualsevol dispositiu que es connecti per un port concret a la Raspberry podrà veure el vídeo en temps real.

Així doncs inicio la gravació i emissió del vídeo mitjançant l’execució d’una d’un fitxer .sh que conté les següents instruccions:

#Ens guardem la data actual
DATACTUAL=`date +"%d-%m-%Y_%H:%M"`

#Obrim sessió permanent i executem l'instrucció
raspivid -a 12 -t 0 -w 1280 -h 720 -vf -ih -fps 25 -l -o - | tee videos/video$DATACTUAL.h264 | nc -k -l 8082

Si doneu un cop d’ull a la documentació del raspivid veureu que els paràmetres que li passo estan configurant el programa per fer un vídeo de 1280×720, amb rotació vertical, 25 frames per segon, sense limit de temps (ja el pararé jo manualment) i que vull afegir al vídeo un text que marqui la data i la hora de la gravació. Fixeu-vos que al final de la instrucció, quan crido al programa que em permet emetre en streaming li estic especificant que m’ho envii al port 8082. Això serà important posteriorment quan intenteu connectar-nos al streaming des de qualsevol dispositiu. Recordeu que nomes em connecto a la meva Raspberry mitjançant una VPN i per tant no es necessari configurar el router per obrir l’accés a cap port més.

Al final en el vídeo es veu així

Que és vegi un bon tros del sostre té una bona explicació. Tal com esta posicionada la càmera, en el cas de que l’intrús s’apropi molt a la càmera seguira essent possible veure-li el rostre.

Amb aquesta configuració l’únic que haureu de fer per veure el vídeo des de el mòbil seria tenir instal·lat un programa capaç de connectar-se a un streaming (per exemple el VLC) e introduir l’adreça d’aquesta manera.

tcp:/h264://<direcció_ip_interna_de_la_raspberry>:8082

Heu de tenir en compte que esteu emetent un vídeo en qualitat 720p i que per tant es possible que l’streaming a vegades es talli un segon en funció de la vostre connexió, però aquests petits talls nomes es veuran duran l’streaming, el vídeo que queda guardat en la SD es veurà perfectament.

Gestió a traves de serveis

Per últim, si voleu que quedi més professional podeu crear dos serveis de manera que els pugueu encendre i apagar més còmodament o inclús configurar-los perquè s’arrenquin automàticament al encendre la Raspberry.

Haureu de crear un fitxer a lib/systemd/system/<nom_del_servei>.service amb el següent contingut

[Unit]
Description=Descripció del servei
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/<instrucció_per_arrancar_script>

[Install]
WantedBy=multi-user.target

Haureu permetre l’execució del fitxer

sudo chmod 644 /lib/systemd/system/<nom_del_servei>.service

Posteriorment, si volguéssiu que s’inicies automàticament al arrancar la Raspberry, hauríeu d’executar aquestes línies

sudo systemctl daemon-reload 
sudo systemctl enable <nom_del_servei>.service

Un cop hagi arrancat podeu comprovar que el servei s’ha iniciat correctament amb la instrucció

sudo systemctl status <nom_del_servei>.service

Per arrancar-lo i parar-lo manualment feu servir les següents instruccions:

sudo service <nom_del_servei> start
sudo service <nom_del_servei> stop

Si tinguéssiu algun problema per crear els serveis, en aquesta adreça trobareu el procediment més detallat

Conclusió

Es un projecte força interessant, senzill i econòmic si ja disposes d’una Raspberry Pi, però requereix d’uns coneixements bàsics de programació i no tenir por de la electrònica. A diferencia d’una alarma comercial o d’un servei de seguretat privada, en el que tot està més integrat i automatitzat, nosaltres haurem de fer encaixar diferents components (Raspberry, sensor de moviment, càmera, RaspController, VLC, programació en Python i programació en Bash) que ben configurats et poden donar unes prestacions bastant acceptables. Podríeu inclús replicar aquest projecte en totes les habitacions que consideréssiu necessari, nomes hauríeu de canviar el missatge que s’envia a xat per tal de saber quina es la Raspberry a la que ens tenim que connectar per encendre l’streaming.

S’hauria de provar que passa si es replica aquest projecte en una Raspberry Zero. Òbviament per la part de programació i del sensor de moviment no hi hauria problema però la part de l’streaming podria ser més problemàtica per la capacitat de computació d’aquests dispositius. En aquest cas potser seria més útil col·locar la càmera en un lloc elevat de manera que difícilment l’intrús es surti de pla en el temps que la Raspberry llença i processa la fotografia i no sigui necessari gravar un vídeo ni connectar-se en streaming.

Leave a comment

Your email address will not be published. Required fields are marked *