Control de cámara IP usando servos y un servidor web en Raspberry pi
Introducción
Contenidos
En este artículo vamos a mostrar cómo controlar mediante una página web una cámara IP, creando una cámara motorizada. Para ello, utilizaremos servo motores, un poco de Python y el servidor Flask. La imagen de la cámara podrá verse en una página web junto a los controles para moverla. Mira a continuación una imagen de cómo quedan la interfaz web, y un pequeño vídeo de qué sucede cuando movemos los sliders. Al ser un proyecto que combina varios componentes, resulta un poco más complejo, pero confiamos en que te guste el resultado y te animes a ponerlo en marcha. Si tienes cualquier duda, te esperamos como siempre en los comentarios. ¿Te animas a ponerlo en marcha?
¿Qué necesitamos para el proyecto?
- Raspberry Pi: hemos usado Raspbian Jessy, pero seguramente el proyecto funcione en cualquier versión posterior (como Stretch)
- Cámara USB: la típica cámara USB que enchufas en el ordenador.
- Estructura para mover en horizontal y vertical (pan/tilt)
- Sin montar (https://www.amazon.de/Two-Axis-Platform-structure-assembly-Instructions-Black/dp/B077S3YZWD/ref=sr_1_4?ie=UTF8&qid=1523908240&sr=8-4&keywords=pan+tilt+bracket+kit)
- Montada (https://www.amazon.de/Tilt-Kit-Frame-SG90-Servos/dp/B01LWKKLMO/ref=sr_1_2?ie=UTF8&qid=1523908240&sr=8-2&keywords=pan+tilt+bracket+kit)
- Servos en caso de comprar la estructura sin montar (https://www.amazon.de/Mopei-MG90S-Metal-Flugzeug-Helikopter-4-St%C3%BCck/dp/B06XQD18QN/ref=sr_1_1?ie=UTF8&qid=1523908269&sr=8-1&keywords=servo+mg90s)
Instalar la cámara USB
Hacealgún tiempo publicamos un artículo para instalar una cámara USB y hacer streaming de vídeo con nuestra raspberry pi. En ese artículo te mostramos todo lo que necesitas para instalar la cámara, que es un requisito para este tutorial. Pulsa aquí para ver el artículo: Cómo instalar una cámara web usb y acceder a ella desde tu navegador.
Conectar los Servos
Para conectar los servos, seguiremos el tutorial que creamos en la página: Controlar un servo motor con Rasperry Pi. Utilizamos la librería pigpio para enviar los pulsos necesarios para mover los servos. Si quieres saber un poco más sobre qué son los servos, echa un vistazo aquí o pregunta tus dudas en los comentarios.
En este proyecto, utilizaremos los pines GPIO 2 (giro horizontal) y GPIO 3 (giro vertical) para controlar los servos, aunque si los tienes ocupados con otro proyecto solo tienes que cambiar los pines en el código que te mostraremos en los siguientes apartados. Observa cómo también hemos conectado el pin 5 a la misma GND que la fuente de alimentación.
Nota: para evitar culaquier daño a la Raspberry pi debido a los motores, hemos utilizado una fuente de energía independiente. Como los servos funcionan a 5V, puedes alimentar con la misma fuente de alimentación tanto a la raspberry como a los motores, siempre que tu fuente tenga suficiente potencia. De todas formas, en todos los proyectos con motores recomendamos que sencillamente los alimentes por separado. No alimentes los motores con las salidas de voltaje de la Raspberry: acabarás chamuscando algo.
Instalar Flask
Flask es un servidor que se programa en Python, lo que nos permitirá utilizar código para controlar nuestra raspberry pi dentro del servidor. Para instalar Flask, te recomendamos seguir nuestro tutorial sobre Flask. De este modo, te familiarizarás con el servidor y cómo lo empleamos para poner en marcha nuestro código.
Preparando el código
Crea los ficheros del proyecto
El proyecto necesita dos ficheros. Uno se trata de la página web, donde tendremos el código html que interactúa con el servidor. El otro es el código del servidor, que recibe las instrucciones de la página web y controla los servo motores que mueven la cámara. En las secciones posteriores podrás copiar el código y pegarlo en los ficheros que te mencionamos a continuación.
- Crea un directorio para el proyecto, llámalo proyecto_camara.
- En ese directorio deberás poner el código del servidor. Llama al fichero codigo_servidor.py (el código lo encontrarás más abajo, sencillamente cópialo en el editor.).
- Dentro del diretorio proyecto_camara, crea un subdirectorio llamado templates. En ese directorio deberás poner el código html. Llama al fichero index.html (el código lo encontrarás más abajo, sencillamente cópialo en el editor).
Código html para la página web
El código de template tiene que colocarse en el subdirectorio proyecto_camara/templates. Cada vez que alguien conecte a la dirección de la raspberry pi en el puerto 5000, el servidor enviará esta template, que tiene los siguientes contenidos:
- Visualización de la cámara web.
- Controles de movimiento horizontal y vertical.
- Código que realiza las llamadas al servidor para mover los motores.
- Alguna modificación en el css de la página para mejorar el aspecto.
Parametrización del código html
Al tratarse de un prototipo, tendrás que ajustar algunos de los parámetros dentro del código para que el proyecto funcione. Te explicamos a continuación las partes que tendrás que modificar y cómo hacerlo para adaptarlo a tu instalación.
Streaming de cámara
<img src="http://192.168.2.121:8081" height="400" width="400" />
En este parámetro, pondremos la IP de la cámara web. Normalmente, se tratará de la misma raspberry pi, pero puedes tener la cámara instalada en otro lugar y acceder a ella a través de su IP.
Rango de los motores vertical y horizontal (calibrando los servos)
Los servomotores se posicionan en función de la duración de los pulso de la señal que les eviamos en PWM. Aquí hemos calibrado los valore mínimos y máximos para las posiciones que ocupan los servos. Si bien el datasheet indica un rango más completo de movimiento para los servos, en la práctica no podemos mover completamente el servo en todos los ángulos debido a la estructura que hemos usado para sujetar la cámara. Básicamente, jugamos un poco con los valores, ampliando el rango si vemos que el servo se puede mover fuera de ese rango, y si vemos que se atasca en una u otra dirección disminuyéndolo.
<input type="range" class="slidervertical" min="1630" max="2430" oninput="servo2(this.value)" onchange="servo2(this.value)" /> <input type="range" class="slider" min="625" max="2430" oninput="servo1(this.value)" onchange="servo1(this.value)" />
IP del servidor
La IP del servidor se puede escribir en la inputbox en la pantalla. Por defecto tenemos el valor de nuestra Raspberry de prueba, pero tendrás que sustituirlo por la dirección IP de la tuya.
<INPUT TYPE="text" NAME="inputbox" VALUE="192.168.2.121">
Código completo html
Aquí tienes el código completo html. Recuerda parametrizarlo para tus motores y tu servidor. Recuerda que es un prototipo, si eres un desarrollador web y tienes ganas de prepararnos una template más resultona, estaremos encantados de publicarla ;-).
<!doctype html> <html> <head> <title>Control de cámara vertical y horizontal</title> <style> .slidecontainer { width: 100%; /* Width of the outside container */ } /* The slider itself */ .slider { -webkit-appearance: none; /* Override default CSS styles */ appearance: none; width: 400px; /* Full-width */ height: 25px; /* Specified height */ background: #d3d3d3; /* Grey background */ outline: none; /* Remove outline */ opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */ -webkit-transition: .2s; /* 0.2 seconds transition on hover */ transition: opacity .2s; } .slidervertical { -webkit-appearance: slider-vertical; /* Override default CSS styles */ appearance: none; width: 25px; /* Full-width */ height: 400px; /* Specified height */ background: #d3d3d3; /* Grey background */ outline: none; /* Remove outline */ opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */ -webkit-transition: .2s; /* 0.2 seconds transition on hover */ transition: opacity .2s; } /* Mouse-over effects */ .slider:hover { opacity: 1; /* Fully shown on mouse-over */ } /* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ .slider::-webkit-slider-thumb { -webkit-appearance: none; /* Override default look */ appearance: none; width: 25px; /* Set a specific slider handle width */ height: 25px; /* Slider handle height */ background: #4CAF50; /* Green background */ cursor: pointer; /* Cursor on hover */ } .slider::-moz-range-thumb { width: 25px; /* Set a specific slider handle width */ height: 25px; /* Slider handle height */ background: #4CAF50; /* Green background */ cursor: pointer; /* Cursor on hover */ } .slidervertical:hover { opacity: 1; /* Fully shown on mouse-over */ } /* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ .slidervertical::-webkit-slider-thumb { -webkit-appearance: slider-vertical; /* Override default look */ appearance: slider-vertical; width: 25px; /* Set a specific slider handle width */ height: 25px; /* Slider handle height */ background: #4CAF50; /* Green background */ cursor: pointer; /* Cursor on hover */ } .slidervertical::-moz-range-thumb { width: 25px; /* Set a specific slider handle width */ height: 25px; /* Slider handle height */ background: #4CAF50; /* Green background */ cursor: pointer; /* Cursor on hover */ } </style> </head> <body> <h1> <b> Control de cámara vertical y horizontal </b> </h1> <div id="container"> <img src="http://192.168.2.121:8081" height="400" width="400" /> <input type="range" class="slidervertical" min="1630" max="2430" oninput="servo2(this.value)" onchange="servo2(this.value)" /> </div> <div class="slidecontainer"> <input type="range" class="slider" min="625" max="2430" oninput="servo1(this.value)" onchange="servo1(this.value)" /> </div> <div style="margin: 0; width:400px; height:80px;"> <FORM NAME="form" ACTION="" METHOD="GET"> IP de tu raspberry pi: <INPUT TYPE="text" NAME="inputbox" VALUE="192.168.2.121"> </FORM> </div> </body> <script> function servo1(position) { IP = form.inputbox.value; console.log(position) address = "http://" + IP +`:5000/move1/${position}`; var xhttp = new XMLHttpRequest(); xhttp.open("POST", address , true); xhttp.setRequestHeader("Content-type", "application/json"); xhttp.send(); } function servo2(position) { IP = form.inputbox.value; console.log(position) address = "http://" + IP +`:5000/move2/${position}`; var xhttp = new XMLHttpRequest(); xhttp.open("POST", address , true); xhttp.setRequestHeader("Content-type", "application/json"); xhttp.send(); } </script> </html>
Código del servidor Flask
El código del servidor flask es muy sencillo. Tiene dos rutas para posicionar los servos, y una ruta para realizar un test (que simplemente mueve los servos de un lado a otro para estar seguros de que lo tenemos todo bien conectado). Recuerda que es un prototipo muy básico. Si eres un desarrollador python con experiencia en Flask y quieres ganas de preparar un código más profesional, ¡estaremos encantados de publicarlo!
# -*- coding: utf-8 -*- from flask import Flask from flask import render_template import time import pigpio import threading app = Flask(__name__) pi = pigpio.pi() # Connect to local Pi. # set gpio modes pi.set_mode(2, pigpio.OUTPUT) in_movement_1 = False last_run_1 = time.time() in_movement_2 = False last_run_2 = time.time() @app.route('/') def hello_world(): return render_template('index.html') @app.route('/move1/<pos>', methods=['GET', 'POST']) def move1(pos): global in_movement_1 global last_run_1 pi.set_servo_pulsewidth(2, pos) last_run_1 = time.time() if not in_movement_1: in_movement_1 = True threading.Thread(target=unlock_servo_1).start() return "ok" def unlock_servo_1(): global in_movement_1 while (time.time()-last_run_1) < 1: time.sleep(0.5) pi.set_servo_pulsewidth(2, 0) # stop servo pulses in_movement_1 = False @app.route('/move2/<pos>', methods=['GET', 'POST']) def move2(pos): global in_movement_2 global last_run_2 pi.set_servo_pulsewidth(3, pos) last_run_2 = time.time() if not in_movement_2: in_movement_2 = True threading.Thread(target=unlock_servo_2).start() return "ok" def unlock_servo_2(): global in_movement_2 while (time.time()-last_run_2) < 1: time.sleep(0.5) pi.set_servo_pulsewidth(3, 0) # stop servo pulses in_movement_2 = False @app.route('/test') def run_test(): # start 1500 us servo pulses on gpio2 pi.set_servo_pulsewidth(2, 1500) time.sleep(1) for _i in range(5): #loop between -90 and 90 degrees pi.set_servo_pulsewidth(2,2500) time.sleep(1) pi.set_servo_pulsewidth(2,600) time.sleep(1) pi.set_servo_pulsewidth(2, 1500) time.sleep(1) pi.set_servo_pulsewidth(2, 0) # stop servo pulses # pi.stop() # terminate connection and release resources return '¡Completado el test!' @app.route('/end') def end_test(): pi.set_servo_pulsewidth(2, 0) # stop servo pulses pi.stop() # terminate connection and release resources
Lanzar el servidor de video
Tal como describimos en el artículo de instalación de una cámara de vídeo, el servidor de vídeo se lanza de la siguiente manera:
[email protected]:~ $ sudo systemctl start motion
Lanzar el servidor Flask
Una vez tienes todos los ficheros del proyecto, para lanzar el servidor flask vamos a emplear los siguientes pasos:
Crear el entorno virtual
Los siguientes pasos sólo serán necesarios la primera vez que creemos el entorno virtual. Lo que hacen es crear un entorno virtual en el que trabajaremos (recuerda: el entorno virtual es muy útil cuando estás trabajando en muchos proyectos en python, pues instala las librerías localmente en este entorno y no nos causará problemas con otros proyectos.
[email protected]:~/proyecto_camara$ virtualenv entornovirtual (entornovirtual) [email protected]:~/proyecto_camara$ . entornovirtual/bin/activate [email protected]:~/proyecto_camara$ pip install Flask [email protected]:~/proyecto_camara$ pip install pigpio [email protected]:~/proyecto_camara$deactivate
Ejecutar el servidor
Estos pasos son los necesarios para trabajar una vez lo tienes instalado todo. Lo que hacen es lanzar el demonio de pigpio (para controlar los servos) y poner en marcha el servidor de Flask.
[email protected]:~/proyecto_camara$ sudo pigpiod [email protected]:~/proyecto_camara$ virtualenv entornovirtual (entornovirtual) [email protected]:~/proyecto_camara$export FLASK_ENV=development (entornovirtual) [email protected]:~/proyecto_camara$export FLASK_APP=codigo_servidor.py (entornovirtual) [email protected]:~/proyecto_camara $ flask run --host=0.0.0.0 * Serving Flask app "codigo_servidor.py" (lazy loading) * Environment: development * Debug mode: on * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 120-054-499
Y si todo ha ido bien, el siguiente paso es el más satisfactorio, ya que vamos a ver el streaming de vídeo en el servidor, pudiendo controlarlo desde nuestro navegador. Si te sale cualquier mensaje de error, puedes contactar con nosotros en los comentarios.
Acceder al servidor web
¿Estás listo? ¡Este es el paso definitivo! Si todo funciona perfectamente, tendrás un streaming de vídeo en el que podrás controlar la posición horizontal y vertical de la cámara mediante barras de desplazamiento en el navegador. Mira a continuación algunas capturas de pantalla.
![]() |
![]() |
Posibles usos del proyecto e ideas
Te mostramos a continuación algunos proyectos e ideas de lo que puedes hacer con estos programas. ¡Esperamos tus aportaciones en los comentarios!
- Cámara de seguridad: podrás mover la cámara de derecha a izquierda y arriba y abajo, de manera manual o automatizada, y grabar el streaming de vídeo en un servidor.
- Vehículo con cámara: ya sea un coche teledirigido, o un quadricóptero, puedes añadirle una cámara y controlarla remotamente para mostrar los vídeos a tus amigos.
- Cámara web accesible para todo el mundo: ¿Qué te parecería publicar un streaming de vídeo en internet, y permitir a todo el mundo mover la cámara para ver distintas zonas? Puedes instalar esta cámara en una playa, una zona de surf, un campo de golf, ¡donde quieras!
¿Te ha gustado? ¿Quieres hacerlo tú? ¿Tienes alguna duda? ¡Escríbenos un comentario!
Hola, necesito ayuda urgentemente, conseguí que todo funcionase perfectamente pero de un día a otro ya no me funciona y no he tocado nada del codigo. Ahora cuando muevo las barras laterales en el terminal no veo que se estén mandando la información a los pines y los servos no se mueven. Por favor si alguien me puede ayudar estaría muy agradecido
Hola, he seguido todos los pasos y al entrar a la pagina (en este caso mi ip 192.168.1.36:5000) me sale la siguiente pagina con los errores:
flask.cli.NoAppException
flask.cli.NoAppException: Could not import «codigo_servidor».
Traceback (most recent call last)
File «/usr/lib/python3/dist-packages/flask/cli.py», line 325, in __call__
self._flush_bg_loading_exception()
File «/usr/lib/python3/dist-packages/flask/cli.py», line 313, in _flush_bg_loading_exception
reraise(*exc_info)
File «/usr/lib/python3/dist-packages/flask/_compat.py», line 35, in reraise
raise value
File «/usr/lib/python3/dist-packages/flask/cli.py», line 302, in _load_app
self._load_unlocked()
File «/usr/lib/python3/dist-packages/flask/cli.py», line 317, in _load_unlocked
self._app = rv = self.loader()
File «/usr/lib/python3/dist-packages/flask/cli.py», line 372, in load_app
app = locate_app(self, import_name, name)
File «/usr/lib/python3/dist-packages/flask/cli.py», line 246, in locate_app
‘Could not import «{name}».’.format(name=module_name)
flask.cli.NoAppException: Could not import «codigo_servidor».
The debugger caught an exception in your WSGI application. You can now look at the traceback which led to the error.
To switch between the interactive traceback and the plaintext one, you can click on the «Traceback» headline. From the text traceback you can also create a paste of it. For code execution mouse-over the frame you want to debug and click on the console icon on the right side.
You can execute arbitrary Python code in the stack frames and there are some extra helpers available for introspection:
dump() shows all variables in the frame
dump(obj) dumps all that’s known about the object
Brought to you by DON’T PANIC, your friendly Werkzeug powered traceback interpreter.
Cual es el error? gracias