Icono del sitio Arnau Dunjó Workspace

Alarma domestica con Raspberry Pi

Como algunos sabréis, raíz del primer confinamiento decidí comprarme una Raspberry Pi 4 para hacer mis experimentos (aprender en funcionamiento de Docker y acostumbrarme a utilizar linux desde la shell).

A pesar de que hay un montón de proyectos muy interesantes para la Raspberry, la verdad es que me ha costado encontrar un proyecto que tenga una utilidad para mí ( aparte del servidor DNS y VPN que comento en este artículo ).

A raíz de una conversación familiar surgió la idea de hacer una alarma. Se pueden implementar diferentes tipos de alarma:

Objetivo

Implementar una alarma que sea capaz de enviarme una notificación en mi dispositivo móvil cuando detecte un intruso. Para asegurar de que no se trata de un falso positivo o de un acceso autorizado (familia) deberá permitir que pueda ver quién ha accedido a mi domicilio (preferiblemente en tiempo real). Además, aprovechando que ya tengo implementado el servidor VPN, podremos gestionar la alarma desde cualquier lugar con “total” seguridad.

Material necesario

Lo menos que necesitamos es:


Os dejo el enlace de un par de modelos de la cámara, modelo 1 , modelo 2 ya que cuesta encontrar una que cumpla con todos los requisitos.

Configuración previa necesaria

La Raspberry debería tener una mínima de configuración previa.

Electrónica

Ahora que ya sabemos todo lo necesario para seguir este artículo, vamos al grano.

Sensor de movimiento

Como puede ver en la imagen, el sensor de movimiento tiene las siguientes partes:

Esquema de connexiones

Os recomiendo que los dos potenciómetros estén al máximo hacia la izquierda que sea posible (mirando el detector tal como esta en la imagen), de manera que tanto el “delay” como la sensibilidad estén al mínimo.

Por otro lado tenemos el esquema de pins de la Raspberry 4 (también es válido para la 3 y la Zero)

Esquema de PINS de Raspberry 4, 3 y Zero

Ahora ya tenemos toda la información para poder conectar el sensor a la Raspberry. Como veréis en la imagen he usado un alargador (macho-hembra) para poder buscar una mejor posición para el sensor. Como el sensor es bastante potente enviará la señal si detecta un movimiento en el 80% de la superficie del comedor (unos 3m cuadrados)

Conexión del sensor de movimiento

La “traducción” en la vida real de la conexión a la placa, lo tenéis en la siguiente imagen, donde se puede ver que finalmente utilicé el pin 2 por VCC (cable negro), el pin 9 por la tierra (cable gris) y el 15 por la salida (cable blanco). Los otros dos cables rojos son los ventiladores de la Raspberry, no hagáis caso)

Conexión del sensor de movimiento a los pins de la Raspberry

En cuanto a hardware ya hemos hecho lo más difícil. Ya veréis que la conexión de la cámara es bastante más simple.

Cámara

Como os comentaba en los requisitos, en mi caso me decanté por una cámara con mucho FOV (field of view). Para los que no estén familiarizados eso quiere decir que tiene una óptica gran angular para que se vea la máxima superficie posible de modo que no tenga que poner más cámaras para cubrir todo el comedor. Distorsiona un poco la imagen pero no estamos buscando hacer una foto de retrato, sino ver que esta pasando en aquella área. También es importante que el enfoque sea ajustable, de lo contrario sería posible que veas el intruso pero sea imposible de identificar ya que no se verá con suficiente nitidez.

Obviamente es muy importante que sea capaz de hacer fotografías o vídeos nocturnos, por lo tanto la cámara debe incorporar luz infrarroja. Con el fin de ahorrar energía y temperatura busqué una que llevara un sensor de luz de manera que graduara automáticamente la intensidad de la luz infrarroja que emite en función de la luz ambiental.

A diferencia del detector de movimiento, la Raspberry Pi tiene un puerto especifico para conectar una cámara

Como veis en las fotografías no queda muy disimulado pero os puedo asegurar que es totalmente funcional y muy práctico. Con una impresora 3D se podría hacer algo más para fijar y ocultar mejor tanto el sensor como la cámara.

No queda muy escondido, pero es totalmente funcional

Finalmente el vídeo queda como se ve en la siguiente imagen. Como puede observarse en mi caso es muy útil que tenga 175 grados de FOV ya que se ve todo el comedor incluyendo la salida a la terraza. La idea es que el ángulo coincida con todo el campo de actuación del sensor de movimiento. Como veréis más adelante en el apartado de programación, no estoy grabando ni a FullHD que es el máximo que permite la cámara. Hay que tener en cuenta que no sólo deseamos grabar un vídeo sino que quiere hacer streaming. Con el fin de no saturar ninguna de las dos cosas, escogí no utilizar la máxima resolución de la cámara. En este sentido hay que decir que a pesar de ello se experimentan ciertos cortes en el streaming, por lo tanto si quisierais un flujo sin interrupciones debería bajar aún más la calidad del vídeo. De todos modos estos pequeños cortes sólo se ven en el streaming, en cambio el vídeo que se guarda en la SD es totalmente fluido.

La superficie que se ve, con la fecha y la hora incrustada en el vídeo
Así se ve de noche

Programación

Una vez que tenga todo el hardware instalado, ya podéis empezar a pensar qué funcionalidad desea que tenga. La idea básica es que cuando el sensor de movimiento detecte que ha pasado algo por delante active el pin 15 y el script de Python nos envíe una notificación al móvil mediante el chat de Telegram que tiene con el bot.

Creación de un bot en Telegram

El primer paso será crear el bote de Telegram que nos permita comunicarnos con la Raspberry. Para crear el bot recomiendo hacerlo mediante el BotFather .

Primero deberéis entrar en Telegram y buscar “BotFather”. Según entrar en chat de BotFather nos mostrará una ayuda de las diferentes funcionalidades de la misma. En resumen, podéis crear órdenes, ponerle una imagen en su bote, una descripción, crear juegos, … Lo importante es crear el bot y obtener un “token” del bot, que es lo que os ha de identificar como desarrolladores del bot y que deberéis mantener en secreto si no deseáis que cualquier otra persona pueda modificar el comportamiento vuestro bot. Para crear el bot, teclea / start y después / newbot

El bot os contestara automáticamente preguntando por el nombre del bote crear. Poned el nombre que deseeis.

A continuación, os volverá a pedir un nombre pero que termine en _bot. Poned el mismo nombre anterior pero acabado en _bot . Después de esto nos aparecerá el “token” en pantalla y un vínculo a su bote. Obviamente el token lo deberéis guardar de forma que no sea visible ya que cualquiera podría utilizar el token para usar vuestro bot.

También tendremos que saber nuestro Chat ID. Para ello, la manera más sencilla es, teniendo el cliente de escritorio de Telegram instalado en vuestro ordenador y hacer clic en este enlace . Como veis no es más que un bot hecho por otra persona que se limita a darte tu chat id y vuestro pseudónimo.

Con estos dos parámetros ya puede montar la estructura básica de su programa en Python.

Script de Python

Vamos a revisar cómo hice el script. Lo explico por partes pero no os preocupéis que más adelante dejo todo el script en un solo bloque para que podáis hacer “copy&paste”.

Primero importé las librerías necesarias

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

Como veis la primera nos permitirá leer y escribir los estados de los diferentes PINS (GPIO) de la Raspberry

La librería “time”, para poder llamar las funciones que le permitirán hacer una pausa en la ejecución del programa, la librería ” telepot” que os permitirá comunicaros con el bot de Telegram y por último la ” datetime” para poder trabajar con fechas y horas

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 librería GPIO es muy fácil de usar pero debéis saber que hay dos maneras de hacer referencia a los pins. La primera es por nombre (por ejemplo “GPIO25”, “GND” …) y el otro por el numero del pin (1, 2, 3 …). La manera en la que te refieres a los pins se configura mediante la instrucción GPIO.setmode. Si nos queremos referir a los pins por su nombre escribiréis GPIO.setmode (GPIO.BCM) si por el contrario preferís hacerlo por número escribirás GPIO.setmode (GPIO.BOARD).

Para saber tanto el nombre como los números deberéis buscarlos en el esquema de pins en la documentación de vuestra Raspberry pero para la Raspberry 3,4 y Zero se puede ver en la segunda imagen de este post.

En la segunda línea, inicio el valor del pin 15 a “IN” (1). También inicializo dos variables que me servirán para llevar un control del estado de la alarma y saber cuándo cambia y dos variables donde pondré, por un lado el valor del token que me ha de servir para comunicar con el bot de Telegram y por el otro identificador del chat donde debo enviar mensajes.

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 núcleo del programa es muy sencillo. Se basa en crear un bucle infinito y en cada iteración comparar el estado previo del pin donde el sensor de movimiento nos marca si ha detectado movimiento (en este caso el pin 15) con el estado actual. Si ha cambiado de estado y el pin esta activado significa que ha detectado movimiento. En este caso, escribo la frase “POSIBLE INTRUSO” en el chat de Telegram que tengo con el bot que he creado previamente. Por el contrario, si ha cambiado de estado pero el pin esta inactivo, significa que la alarma está en “reposo”. Como verá cada vez que cambia de estado escribo la fecha y hora para la salida estándar. Esto sólo es para “debuggar”. Es importante respetar los time.sleep (1) ya que sino el programa se saturaría (recuerde estamos dentro de un bucle infinito)

Por último, para poder salir controladamente de la aplicación he añadido las siguientes líneas.

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

Pulsando Crt + c cierro el programa, limpiando los estados de los pins y mostrando un mensaje de que se ha cerrado correctamente.

Os dejo todo el código aquí abajo

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")

Con esto ya funcionaria la alarma, pero nos podría ser útil ver quién está dentro de casa y en este punto entra la cámara.

Una posible solución es hacer una foto en el momento que detecta el movimiento, por ejemplo, justo antes de enviar el mensaje en el chat de Telegram. Para ello debería añadir las siguientes líneas:

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ínea importo la librería “subproceso” que os permitirá la ejecución de otro programa dentro del programa principal como se ve en la línea 3, donde se llama al programa que viene por defecto en la Raspberry para hacer una foto con la cámara. En este enlace podréis ver para que sirven todos los parámetros que le he especificado, pero para resumir le estará indicando que desea una foto de 1280×960, que la desea girar verticalmente, el nombre y la ruta del archivo de la foto y que el modo de exposición se “sport” por lo que desea que dispare rápido con el tiempo de exposición al mínimo. En las líneas 4 y 5 utilizará el bot para enviar la fotografía en el chat del Telegram.

Finalmente el código quedaría así:

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")

Este método tiene el problema que a pesar de configurarse le la exposición “sport” para cuando la Raspberry tira la foto, el intruso ha podido salir del campo de visón de la cámara o aparecer sólo una parte de él. A demás como el script estará utilizando la cámara para hacer la foto, nos bloquea el uso de la cámara para cualquier otro programa, de modo que al final decidí prescindir de la foto y crear otro script que me permitiera grabar que esta pasando y emitirlo en streaming.

Streaming

Para tener un grado mayor de libertad decidí no controlar desde el programa de la alarma el inicio y el fin de la grabación / emisión. Me resultaba difícil decidir cuándo debía parar la grabación y por tanto, creo que la mejor decisión es iniciar manualmente desde el móvil. Como podéis ver en la imagen desde la misma aplicación de RaspController (la que me permite conectar a la Raspberry desde cualquier lugar) también da la posibilidad de guardar instrucciones en accesos directos para no tener que escribirlas cada vez

Así pues me creé un par de archivos .sh para encender y apagar el servicio de streaming.

Para hacer el streaming utilicé la aplicación por defecto de la Raspberry ( raspivid ) para gestionar la cámara y concatenar la salida de éste a otro programa, el tee . Este programa me permite guardar el vídeo en la SD de la Raspberry y al mismo tiempo sacarlo por otra “salida”. A esta segunda salida está el Netcat que me permite emitir en streaming el vídeo que estoy grabando, por lo que cualquier dispositivo que se conecte por un puerto concreto a la Raspberry podrá ver el vídeo en tiempo real.

Así pues inicio la grabación y emisión del vídeo mediante la ejecución de una de un archivo .sh que contiene las siguientes instrucciones:

#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 echáis un vistazo a la documentación del raspivid verá que los parámetros que le paso están configurando el programa para hacer un video de 1280×720, con rotación vertical, 25 frames por segundo, sin límite de tiempo (ya lo pararé yo manualmente) y que quiero añadir al vídeo un texto que marque la fecha y la hora de la grabación. Fijaros que al final de la instrucción, cuando llamo al programa que me permite emitir en streaming le estoy especificando que me lo envíe al puerto 8082. Esto será importante posteriormente cuando intente conectarnos el streaming desde cualquier dispositivo. Recordad que sólo me conecto a mi Raspberry mediante una VPN y por tanto no es necesario configurar el router para abrir el acceso a ningún puerto más.

Al final en el vídeo se ve así

Que se vea un buen trozo del techo tiene una buena explicación. Tal como esta posicionada la cámara, en el caso de que el intruso se acerque mucho a la cámara siguiera siendo posible verle el rostro.

Con esta configuración lo único que tenéis que hacer para ver el vídeo desde el móvil sería tener instalado un programa capaz de conectarse a un streaming (por ejemplo el VLC ) y introducir la dirección de esta manera.

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

Tenga en cuenta que está emitiendo un vídeo en calidad 720p y que por lo tanto es posible que el streaming a veces se corte un segundo en función de vuestra conexión, pero estos pequeños cortes sólo se verán llevarán el streaming , el vídeo que queda guardado en la SD se verá perfectamente.

Gestión mediante servicios

Por último, si quieres que quede más profesional podéis crear dos servicios de forma que los pueda encender y apagar más cómodamente o incluso configurarlos para que se arranquen automáticamente al encender la Raspberry.

Deberá crear un archivo en lib/systemd/system/<nom_del_servicio>.service con el siguiente contenido

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

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

[Install]
WantedBy=multi-user.target

Deberá permitir la ejecución del archivo

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

Posteriormente, si quisierais que se iniciara automáticamente al arrancar la Raspberry, deberíais ejecutar estas líneas

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

Una vez haya arrancado podéis comprobar que el servicio se ha iniciado correctamente con la instrucción

sudo systemctl status <nom_del_servei>.service

Para arrancarlo y pararlo manualmente utilizad las siguientes instrucciones:

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

Si tuviera algún problema para crear los servicios, en esta dirección encontrará el procedimiento más detallado

Conclusión

Es un proyecto bastante interesante, sencillo y económico si ya dispones de una Raspberry Pi, pero requiere de unos conocimientos básicos de programación y no tener miedo a la electrónica. A diferencia de una alarma comercial o de un servicio de seguridad privada, en el que todo está más integrado y automatizado, nosotros tendremos que hacer encajar diferentes componentes (Raspberry, sensor de movimiento, cámara, RaspController, VLC, programación en Python y programación en Bash) que bien configurados te pueden dar unas prestaciones bastante aceptables. Podría incluso replicar este proyecto en todas las habitaciones que consideráramos necesario, sólo debería cambiar el mensaje que se envía a chat para saber cuál es la Raspberry a la que nos tenemos que conectar para encender el streaming.

Se debería probar que pasa si se replica este proyecto en una Raspberry Zero . Obviamente por la parte de programación y del sensor de movimiento no habría problema pero la parte del streaming podría ser más problemática por la capacidad de computación de estos dispositivos. En este caso quizás sería más útil colocar la cámara en un lugar elevado de manera que difícilmente el intruso se salga de plano en el tiempo que la Raspberry tira y procesa la fotografía y no sea necesario grabar un video ni conectarse en streaming.


Salir de la versión móvil