Detección de paso con el sensor de distancia

Después del parón del verano volvemos con un proyecto pequeñito. Después de añadir movimiento (Añadiendo movimiento: servos) descubrimos que podemos orientar la cámara en una habitación (Una cámara móvil), pero casi nunca vemos nada interesante (es difícil encontrar el momento adecuado).

Sentía curiosidad por ver cómo funcionaría un sensor de movimiento y decidí comprar un par de HC-SR04, que funcionan con ultrasonidos.

Ojos que no ven

A post shared by Fernando Tricas García (@ftricas) on

El objetivo es disparar una fotografía cuando alguien pase por delante de la cámara: para ello medimos la distancia al obstáculo que haya enfrente del sensor y cuando cambie tenemos que suponer que hay alguien o algo en la trayectoria.

Tras unas primeras pruebas con la Raspi el resultado no era satisfactorio: se obtienen medidas aproximadas (y relativamente sencillas de filtrar, eliminando los valores desvidados) pero que no resultaban apropiados para lo que teníamos pensado.
Se puede ver, por ejemplo, un tutorial en HC-SR04 Ultrasonic Range Sensor on the Raspberry Pi.

La conexión del sensor a nuestra raspi.

Probando el sensor de distancia #raspi

A post shared by Fernando Tricas García (@ftricas) on

Parece que el problema de precisión tiene que ver con la medición del tiempo (que es lo que se usa para medir la distancia con este tipo de sensores: el tiempo que tarda en ir y volver hasta el obstáculo un frente de ondas sonoras), que no es muy preciso en la Raspberry con su GNU/Linux.

Afortunamdamente, teníamos a mano un Arduino y decidimos probar si era más adecuado. Esto nos permitiría:

– Medir distancias de manera más precisa.
– Aprender a comunicar la Raspberry y el Arduino.

Y, seguramente, abrir la puerta a nuevos proyectos.

El arduino conectado al sensor:

Probando el sensor de distancia #arduino #raspi

A post shared by Fernando Tricas García (@ftricas) on

Siguiendo las pistas de HC-SR04 Ultrasonic Sensor fue fácil preparar el programita (sketch) de arduino y hacer las conexiones (se puede ver en sketch.ino en su versión actual, puede haber cambios después).

Efectivamente, la medición era mucho más precisa: de vez en cuando, manteniendo fijo el sistema hay una variación de un centímetro o dos. Pero eso no es un problema cuando tratamos de detectar la presencia de un objeto más o menos grande (como una persona) en la trayectoria porque debería haber una modificación de más de 20 cm.

Ahora había que comunicar el arduino con la Raspberry (para reaprovechar código existente y no tener que empezar de cero en el nuevo aparatito).
Parece que hay varias formas de comunicarlos: mediante un puerto serie sobre USB (Connect Raspberry Pi and Arduino with Serial USB Cable), mediante I2C (Raspberry Pi and Arduino Connected Using I2C) y mediante GPIO (Raspberry Pi and Arduino Connected Over Serial GPIO).
Elegí la primera porque me pareció la más sencilla de poner en funcionamiento (aunque no descarto probar las otras).

Lo que envía el arduino se puede leer fácilmente en la Raspberry como una entrada de texto y sólo tenemos que procesar adecuadamente:

distAnt=0
...

while 1:
	distAnt = dist
	dist = int(ser.readline().strip().strip())
	
	if abs(distAnt-dist)>10:
		print "Alert!!"

Esto es: conservamos la medición anterior (distAnt), obtenemos una nueva (dist = …) y si la diferencia es mayor que diez, lanzamos una alerta.

Como lo que queríamos hacer era una fotografía justo en ese momento, hemos reutilizado código de Una cámara en la Raspberry Pi y, siguiendo viejas costumbres, la envíamos por correo (Enviar una imagen por correo en Python).

El código puede verse en serialPicture.py.

Por la forma de enviar el mensaje esto dejaba el sistema inutilizado mucho tiempo: no podemos evitar el tiempo que consume la cámara (que no es despreciable tampoco) porque no queremos poner más de una; pero sí que podemos evitar la espera de la conexión al servidor de correo, y el envío de la imagen.
Esto lo conseguimos mediante la creación de un subproceso (ver multiprocessing) que se ocupa del envío.

camera(name,cam)
p = Process(target=mail, args=(name,who))
p.start()

Esto es, tomamos la foto y lanzamos un proceso hijo que se ocupa del envío.
No tenía experiencia con esta parte de Python y no estoy seguro de si hace falta terminar el proceso de alguna forma, pero como no hay que hacer sincronizaciones ni esperar a que termine para hacer otras cosas, parece que funciona razonablemente.

Para terminar: ninguno de los procesos es muy rápido; que nadie espere poder utilizar esto como ‘trampa’ para capturar el paso de un ave volando (o incluso un niño corriendo).

¿Qué podemos hacer ahora?
Podríamos embarcar el sensor de distancia en uno de nuestros motores (como en Una cámara móvil) y con ello hacer un ‘mapa’ de la habitación: se me ocurre que serviría para detectar cambios de otra forma diferente.
Otra cosa que se me ocurre es que, una vez que alguien o algo ha cruzado la ‘barrera’ podríamos hacer un barrido con la cámara móvil y tomar varias imágenes (o incluso grabar un vídeo; me está dando pereza, pero es algo que hay que probar).
Y, por supuesto, escuchar ideas de quien lea esto o buscarlas por ahí.
Todavía hay otro problema y es que el sensor funciona aunque no haya luz; tal vez podríamos añadir un sensor de luminosidad para evitar el disparo cuando sabemos que no se va a poder tomar una imagen (o, tal vez, iluminar la escena de alguna forma).

Una cámara móvil

Una vez que tenemos un bot que nos permite controlar remotamente nuestro proyecto (Segundo bot: avanzamos) y sabemos mover los motores (Movimiento suave con los servos) llega el momento de montar la cámara encima (Una cámara en la Raspberry Pi) y controlarla remotamente.
Recordemos que el control se hace mediante XMPP (por ejemplo, con programas como Pidgin, Google Talk o nuestro cliente de mensajería favorito); la idea era evitar abrir puertos en el router para controlarlo vía web y, sin embargo, poder enviar instrucciones desde internet sin problemas.

Para el montaje seleccionamos un par de cajas (como forma barata y simple de proporcionar el soporte para todo). En una caja más grande hicimos un par de agujeros (para poder colocar dos motores, aunque finalmente sólo hemos utilizado uno):

Hemos pintado la caja #raspi

A post shared by Fernando Tricas García (@ftricas) on

Dentro de la caja van las conexiones (baterías para alimentar los motores, y cables para controlarlos desde la Raspberry Pi, que se queda fuera de la caja).

Caja como soporte para los motores

A post shared by Fernando Tricas García (@ftricas) on

La cámara se monta en una caja más pequeña que se sujeta al servo seleccionado.

Y tenemos un prototipo de mejor aspecto #raspi

A post shared by Fernando Tricas García (@ftricas) on

Cuando se le da la instrucción de movimiento, la cámara va a la posición elegida, se detiene para hacer la foto y la envía por correo. Finalmente, vuelve a su posición inicial.
Toda la secuencia puede verse en el siguiente vídeo.

El código del proyecto está disponible en err-plugins (puede evolucionar más adelante; el código en su estado actual puede verse en pruebas.py).

Recientemente se publicó un proyecto similar en “Raspberry Eye” Remote Servo Cam. Tiene dos diferencias fundamentales: se han incluido movimientos en dos ejes (nuestro proyecto sólo se mueve a derecha e izquierda) y se controla mediante una página web.

¿Qué haremos a continuación?
Tengo varias ideas, pero no se todavía qué haré: sería interesante que la cámara tuviera cierta autonomía (¿detección de movimiento o cambios en la escena?); tampoco me importaría pensar en movilidad real (¿embarcar la cámara en algún tipo de dispositivo con ruedas? me encantó este hexápodo).
Yendo más allá, tal vez podríamos pensar en otros dispositivos de control (¿wearables?).

Por supuesto, ideas, comentarios, sugerencias… Serán bienvenidos.

Movimiento suave con los servos

Uno de los problemas que tienen los servos es que su movimiento es bastante brusco, como podía apreciarse en el vídeo que pusimos en Añadiendo movimiento: servos. Con el montaje que había pensado eso era un problema, porque la cámara tiene un cierto peso y en alguna ocasión podía salir disparada, o desencajarse, como puede imaginarse viendo:

Más pruebas #frikeando #voz #motores #raspi #c3po

A post shared by Fernando Tricas García (@ftricas) on

La solución para el problema es relativamente sencilla: cuando queremos llevar el motor a una determinada posición podemos ir haciéndolo con pequeños pasos. La idea es indicarle una sucesión de posiciones cada vez más próxima al objetivo. Así, aunque los movimientos son bruscos, al ser pasos cortos no afectan tanto al objeto que va montado sobre el motor (en este caso la cámara).
El código podría ser como el de esta función:

def move(self, servo, pos, posIni=MIN, inc=10):

	servoGPIO=18
	servoGPIO=17
	posFin=posIni + (MAX-MIN)*pos
	steps=abs(posFin - posIni) / inc

	print "Pos ini", posIni
	print "Pos fin", posFin
	print "Steps", steps
	print int(steps)

	if pos < 0:
		pos = -pos
		sign = -1
	else:
		sign = 1

	for i in range(int(steps)):
		servo.set_servo(servoGPIO,posIni+10*i*sign)
		time.sleep(VEL)

	print "Pos ini", posIni
	print "Pos fin", posFin
	print "Steps", steps
	print int(steps)

	servo.stop_servo(servoGPIO)

Esto es, suponiendo que partimos de una posición inicial (posIni) y que queremos movernos un cierto porcentaje (un número real entre 0 y 1) calculamos la posición final sabiendo el recorrido total (MAX-MIN):

posFin=posIni + (MAX-MIN)*pos

Y calculamos el número de pasos que daremos basándonos en un incrementos de 10 (inc=10):


steps=abs(posFin - posIni) / inc

Se usa el valor absoluto porque el movimiento puede ser en ambas direcciones (según en qué punto esté el motor), de lo que se ocupa el condicional:


if pos < 0:
...

Finalmente, se hacen los movimientos con el bucle:

for i in range(int(steps)):
	servo.set_servo(servoGPIO,posIni+10*i*sign)
	time.sleep(VEL)

El resultado se parece al que se aprecia en este vídeo:

Montamos la cámara en el motor que se mueve más despacio #raspi

A post shared by Fernando Tricas García (@ftricas) on

Donde se puede observar un movimiento de ida y vuelta con un improvisado modelo. Se puede regular la velocidad con el tiempo que se tarda entre uno de estos pequeños movimientos y el siguiente (parámetro VEL).

Seguramente habría sido mejor elegir otro tipo de motores, pero esto resuelve de manera razonable nuestro problema.

Segundo bot: avanzamos

En Raspberry Pi: ¿qué temperatura hace en mi casa? hablábamos del primer bot que nos permitía interactuar con la Raspberry Pi desde donde nos encontráramos.

Estuve probando SleekXMPP y phenny pero ambos tenían algunas limitaciones así que continué buscando hasta que encontré otro proyecto interesante, err que, además de estar en ese momento con cierta actividad proporciona una arquitectura modular para añadir características e incluso tiene una comunidad en Google+, Err.

Lo primero que hice fue adaptar las pruebas que ya había hecho con phenny a la nueva arquitectura, y añadirle un par de cosas más relacionadas con la toma de fotos y su envío. Puede verse el código del módulo en err-plugins (en el futuro podría cambiar así que nos fijaremos en la versión actual con sus dos ficheros.

El primero es pruebas.plug, que contiene información sobre el módulo en sí, con la sintaxis que define el bot:

[Core]
Name = Pruebas
Module = pruebas

[Documentation]
Description = let’s try things !

Y el fichero pruebas.py que contiene el código en sí. Esencialmente es un fichero con código en Python. Por ejemplo, para que el bot tome una fotografía nos la envíe, sería:

<br />
@botcmd<br />
def foto(self, msg, args):<br />
	&quot;&quot;&quot;Take a picture&quot;&quot;&quot;<br />
	quien=msg.getFrom().getStripped()<br />
	yield &quot;I'm taking the picture, wait a second &quot;<br />
	if(args):<br />
		try:<br />
			cam=int(args)<br />
		except:<br />
			cam=0<br />
	else:<br />
		cam=0<br />
	yield &quot;Camera %s&quot;%cam<br />
	self.camera(&quot;/tmp/imagen.png&quot;,cam)<br />
	yield &quot;Now I'm sending it&quot;<br />
	self.mail(&quot;/tmp/imagen.png&quot;, quien)<br />
	my_msg = &quot;I've sent it to ... %s&quot;%quien<br />
	yield my_msg<br />

Con la primera línea decimos que es una instrucción para el bot y luego definimos una función que es la que se ocupa de las acciones correspondientes. El nombre de la función será la instrucción (con un prefijo configurable, que permite al bot diferenciar entre lo que se le indica a él y lo que son otros textos que no interpretará).

En nuestro caso, la instrucción:

.foto

Ejecutaría una función que es muy parecida a la que comentábamos el otro día en Una cámara en la Raspberry Pi. Las diferencia serían:

  • Recibe los parámetros a través de la llamada a la función

    def foto(self, msg, args):
  • Responde al que le envió la orden:

    quien=msg.getFrom().getStripped()
  • El argumento puede ser 0, 1 o ninguno (no se hace validación) porque tenemos dos cámaras en la Raspberry. Por defecto dispara con la cámara 0.
  • A continuación nos indica qué cámara hemos elegido:

    yield "Camera %s"%cam
  • Invoca a la función que realmente realiza la fotografía, cuyos parámetros son muy similares a los que ya comentamos en su momento (el nombre del fichero donde se almacenará la foto y la cámara elegida):

    self.camera("/tmp/imagen.png",cam)
  • Invoca a la función de envío por correo, cuyos parámetros son el fichero donde está la imagen y a quién se envía el mensaje.

    self.mail("/tmp/imagen.png", quien)
  • Finalmente responde al que dio la orden, nuevamente con yield.

Si miramos el código, la diferencia de estas dos funciones es que no van precedidas con @bootcmd, así que no están disponibles como instrucciones. Por lo demás necesitan (igual que contábamos en su momento) los parámetros de configuración adecuados.

Errbot nos proporciona un mecanimos para eso, mediante:

<br />
def get_configuration_template(self):<br />
return{'ADDRESS' : u'kk@kk.com', 'FROMADD' : u'kk@kk.com',<br />
'TOADDRS' : u'kk@kk.com', 'SUBJECT' : u'Imagen',<br />
'SMTPSRV' : u'smtp.gmail.com:587', 'LOGINID' : u'changeme',<br />
'LOGINPW' : u'changeme'}<br />

Podemos definir en un diccionario los parámetros que podremos configurar (vistos, recuerden en Una cámara en la Raspberry Pi).

Y, tecleando en nuestro cliente de mensajería:

.config Pruebas

En este caso, Pruebas es el nombre del módulo y el punto (.) es el indicador que hemos elegido para las instrucciones. La instrucción config nos devuelve la configuración que el módulo tenga en ese momento (si no está configurado muestra los valores que hemos puesto en la definición; si ya lo hemos configurado mostrará los valores que tenga). Los valores que se devuelven pueden utilizarse como patrón para configurar el módulo:

.config Pruebas {'TOADDRS': u'mydir@mail.com', 'SMTPSRV':
u'smtp.gmail.com:587', 'LOGINPW': u'mypassword',
...
}

Con esto, ya falta menos para hacer el montaje final. Seguiremos
informando.

Añadiendo movimiento: servos

Tener una cámara (o dos) conectadas a nuestra raspi nos hace descubrir rápidamente una de las molestas limitaciones de las camaritas: ¡no se mueven!.
Afortunadamente, hay disponibles un buen montón de posibilidades para añadir este aspecto. Yo me incliné por adquirir unos servomotores

Motor #raspi

A post shared by Fernando Tricas García (@ftricas) on

Son baratos, ocupan poco sitio y son algo ruidosos.

Como ya hay muchas páginas por ahí explicando los fundamentos de su funcionamiento, recordaremos aquí dos o tres cosillas: tienen un determinado radio de giro (en este caso 180 grados) y la manera que tienen de moverse es yendo a una determinada posición, que se indica mediante el envío de pulsos cuya longitud determina la amplitud del giro (para gente interesada lo cuentan en, por ejemplo How do servos work? -en inglés- o en Trabajar con Servos -en español-).

Desde un programa nuestra misión será encontrar la forma de enviar esos pulsos a través de las conexiones adecuadas (recuerden interacición entre el mundo físico y el ordenador).

Afortunadamente la red está llena de ejemplos y consejos sobre como hacerlo.

Por ejemplo, los programas: servo, servo2, servoYT, y servoYT2 están basados en lo que se ve en el vídeo Servo control using Raspberry pi (y también, pero menos, en Servo Control with the Raspberry Pi).

Los pasos fundamentales serían, siguiendo el tercer programa, primero indicar los módulos que utilizaremos:

import RPi.GPIO as GPIO
import time

El primero sirve para mandar instrucciones a gravés de los pines adecuados de la raspi. El segundo para manejar los datos relativos al tiempo.

Haremos referencia a los pines por su numeración y ponemos el 11 en modo salida:

GPIO.setmode(GPIO.BOARD)

GPIO.setup(11,GPIO.OUT)

Definimos el controlador con una frecuencia de 50Hz y lo ponemos en marcha, llevándolo a la posición central:

p = GPIO.PWM(11,50)

p.start(7.5)

Finalmente, cambiamos de posición cada segundo, poniéndolo en movimiento:

try:
    while True:
        print "Uno"
        p.ChangeDutyCycle(7.5)
        time.sleep(1)
        print "Dos"
        p.ChangeDutyCycle(12.5)
        time.sleep(1)
        print "Tres"
        p.ChangeDutyCycle(2.5)
        time.sleep(1)

Esto es, empieza en el centro y se va moviendo hacia los extremos. Desde el centro va a uno de los extremos, luego al otro y finalmente vuelve a la posición inicial, como puede verse en este vídeo:

Como se ve hacia el final del vídeo, nada nos impide añadir otro motor (más allá de los pines que haya disponibles) que es el código incluido en el cuarto programa, servoYT2).
Allí se ha añadido el pin 12 y se genera otro controlador (ahora son p1 y p2) y simplemente se van alternando las instrucciones enviadas a uno y a otro. En este mini-vídeo los dos motores en acción:

Dos motores #raspi

A post shared by Fernando Tricas García (@ftricas) on

Próximamente explicaremos el montaje para poner la cámara sobre uno de los motores y poder finalizar el proyecto.

Enviar una imagen por correo en Python

Una vez que podemos hacer una fotografía con nuestra webcam (Una cámara en la Raspberry Pi) lo siguiente que nos gustaría hacer es verla desde donde estemos.

Hay por ahí muchos textos explicando como hacer un servidor web en el que mirar la imagen capturada por la cámara, pero a mi ese método no me gustaba demasiado: habilitar un servidor web, abrir algunos puertos en el router casero y emplear algún sistema para dar cuenta de las posibles variaciones en la ip me parece poco robusto. Pero también incluye la posibilidad de que alguien pueda terminar conectándose a ese servidor web y acceder a nuestra red de alguna manera (posibilidad remota, seguramente, pero no despreciable).

También estuve evaluando la posibilidad de enviar las imágenes a través del cliente de mensajería pero no se si no es posible, o no he encontrado documentación para hacerlo, así que también lo descarté.

La elección final recayó en el viejo y confiable sistema de correo electrónico: el bot recibe las peticiones como sea (xmpp, IRC, …) y envía las imágenes captadas por correo electrónico.

Hay mucha documentación sobre cómo enviar un mensaje de correo con un adjunto pero yo ya tenía un programita para hacerlo, así que es el que usé. Se puede ver mail.py.

Esencialmente construye un mensaje a partir de sus componentes (Origen, destino, asunto, adjuntos, …).

Necesita unos cuantos parámetros, que hay que configurar para que funcione correctamente. La forma elegida para la configuración es mediante un módulo auxiliar:

import mailConfig


cuyo único contenido son variables cuyos valores han de ser adaptados. Desde nuestro programa simplemente las leemos (aunque podríamos usarlas directamente, claro).

destaddr = mailConfig.ADDRESS
fromaddr = mailConfig.FROMADD
toaddrs = mailConfig.TOADDRS
subject = mailConfig.SUBJECT
smtpsrv = mailConfig.SMTPSRV
loginId = mailConfig.LOGINID
loginPw = mailConfig.LOGINPW

imgFile = '/tmp/imagen.png'


Por defecto fijamos un nombre para la imagen que enviaremos, aunque desde la línea de invocación podríamos elegir otro.

Tenemos una dirección de envío de correos por defecto (destaddr) pero también podemos pasar una desde la línea de instrucciones (poco robusto, no se verifica nada).

A partir de allí, construímos el mensaje.

Detección y configuración del tipo de objeto que envíamos:

format, enc = mimetypes.guess_type(imgFile)
main, sub = format.split('/')
adjunto = MIMEBase(main, sub)


De esta forma, el programa nos serviría para enviar ficheros de distintos tipos y no sólo imágenes.

Generación del adjunto, codificación y se añade al mensaje que estamos construyendo:

adjunto.set_payload(open(imgFile,"rb").read())
Encoders.encode_base64(adjunto)
adjunto.add_header('Content-Disposition', 'attachment; filename="%s"' % imgFile)
mensaje.attach(adjunto)


Y, finalmente, añadimos el resto de parámetros del mensaje:

mensaje['Subject'] = subject
mensaje['From'] = fromaddr
mensaje['To'] = destaddr
mensaje['Cc'] = toaddrs


El mensaje va vacío, no tiene texto (Ejercicio para el lector: ¿le añadimos un texto? Se me ocurre algo así como: ‘Fotografía tomada el día xx-xx-xxxx a las hh:mm’).

Para terminar, hacemosel envío (negociando directamente con el servidor de
correo en envío):

server = smtplib.SMTP()

server.connect(smtpsrv)
server.ehlo()
server.starttls()
server.login(loginId, loginPw)
server.sendmail(fromaddr, [destaddr]+[toaddrs], mensaje.as_string(0))
server.quit()


Y, ya tenemos un programita en Python que enviará por correo el fichero que le pasemos como parámetro a la dirección que le indiquemos. O a la dirección por defecto, el fichero por defecto.
Además envía siempre una copia a toaddrs para que tengamos constancia de todos los mensajes.

Sobre la configuación, tanto destadrr como fromaddr y toaddrs deberían ser
direcciones de correo válidas.

El servidor smtpsrv puede ser cualquiera que podamos utilizar y el programa
supone que es autentificado (de ahí la necesidad de configurar un usuario y
una contraseña). Por ejemplo, para hacer el envío cifrado a través de los
servidores de Google, podríamos usar:

smtpsrv='smtp.gmail.com:587'


Y como usuario y contraseña uno que utilicemos nosotros habitualmente.

Una cámara en la Raspberry Pi

Ha pasado bastante tiempo desde la última entrada en esta bitácora y mucho más desde que empecé a hablar de mis pinitos con la Raspberry Pi (Raspberry Pi: ¿qué temperatura hace en mi casa? pero eso no quiere decir que haya abandonado el aparatito, que me proporciona mis buenos ratos (y también mosqueos, con las cosas que no se consiguen, o no funcionan como uno espera).

Después de aquel experimento donde accedíamos remotamente a algunos sensores e información de lo que sucedía en casa, pensé en avanzar por dos vías: buscar algún bot que fuera más sencillo de programar (phenny me gustó pero parece que su desarrollo se ha parado; lo de sencillo de programar va en gustos y formas para cada uno, claro). También pensé añadir una cámara al sistema de ‘vigilancia’ casera, y esto me ha consumido bastante tiempo.

La referencia para comprar una cámara que funcione con nuestra Raspberry es RPi USB Webcams. Entre los modelos que vi allí (y estaban disponibles en una tienda cercana) elegí la Logitech C270. En aquel momento entendí que funcionaba bien directamente conectada al puerto USB, cosa que resultó no ser correcta (ahora dice que necesita un hub USB alimentado que es lo que solucionó mis problemas después de muchas pruebas).

En el intermedio, compré (de segunda mano, en una tienda de esas de videojuegos que venden complementos para consolas) una cámara para Playstation PS4 (creo). Había leído por ahí que era menos exigente con los requisitos energéticos y que tal vez podría funcionar (y eran 5 euros de coste, qué caray).

Las cámaras:

Probando otra cámara (PS3)

A post shared by Fernando Tricas García (@ftricas) on

Es cierto que la cámara de la Playstation funcionaba mejor que la otra sin alimentación externa, pero la Raspberry seguía colgándose aleatoriamente (no hay que olvidar que en el otro puerto USB había conectado un dispositivo WiFi, que utilizo para conectarme al cacharrito para poder gestionarlo cuando estoy en casa; y que la conecta a internet de manera casi permanente).

La calidad de imagen es algo peor que la de la de Logitech y tiene dos posiciones (un zoom) que se gestiona manualmente (así que no está accesible para un programa que utilice la cámara). La he conservado como cámara secundaria.

Así que después de un montón de días probando a ratos perdidos y con algo de frustración, fui a la tiena a por un hub USB alimentado (tenía uno que me regalaron pero no estaba en muy buenas condiciones). El elegido fue uno de Trust (el EasyConnect 7 Port USB2 Powered Hub).

Como decía la canción…

A post shared by Fernando Tricas García (@ftricas) on

Mientras tanto, claro, iba probando las diferentes alternativas para manejar la cámara. Vale la pena recordar que salió la cámara oficial del proyecto Raspberry que se supone que nos evitaría los problemas de alimentación pero que no llegué a considerar seriamente: me parecía algo cara (una vez que ya había comprado una, claro) y además utiliza el sistema de conexión con un bus que parece poco práctico (sobre todo si luego queremos usarla para otras cosas).

Seguramente funciona bien, es integrable en proyectos más ambiciosos. Se puede ver información en Raspberry Pi Camera Module.

Para gestionar la cámara hay bastantes programas y ejemplos por ahí:

Y alguno más que seguramente estoy olvidando.

También descubrí OpenCV que permite, entre otras cosas, manejar cámaras desde un programa (en particular se puede hacer en Python): más de una cámara, si tiene parámetros configurables se pueden gestionar desde el propio programa (luminosidad, zoom, contraste, …).

Podemos ver un programita de ejemplo en: cam.py. El programa graba la imagen que toma en un fichero cuyo nombre se le puede pasar en la propia llamada. Si no se pone nombre, elige uno predefinido.

Por si vale la pena, comentamos algunas líneas del código.

el nombre del fichero se define en:


imgFile = '/tmp/imagen.png'

El nombre siempre es el mismo. Podríamos utilizar algo como:


imgFile = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())+'.png'

Para poder conservar las diferentes imágenes que se vayan tomando (cuidado con llenar el espacio de almacenamiento).

Luego hay código para leer el nombre de la línea de instrucciones (si se suministra), aunque no es nada robusto (no se hace niguna validación), para después parsar a inicializar la cámara:


cam=cv2.VideoCapture(0)

La captura de la imagen se hace en una función:


def get_image():
retval, im = cam.read()
return im

Nuevamente es poco robusto, no se comprueba el código de error que puede devolver la función read y simplemente devuelve la imagen capturada (que podría no existir porque algo hubiera fallado).

Esta función se utiliza en:


img=get_image()

Y, finalmente,


cv2.imwrite(imgFile, img)

escribe la imagen en el fichero que hemos definido arriba.

Creo que esta entrada ya es un poco larga. Otro día seguiremos con el bot.

Raspberry Pi: ¿qué temperatura hace en mi casa?

Estaba dudando si este sería el lugar adecuado para poner estas cosas o si debería crear otra bitácora. Visto mi ritmo de trabajo (lento), creo que será más razonable ponerlo aquí e ir contando las cosas que haga, si es que tengo tiempo de hacerlas (y luego de explicarlas).

Vamos a hablar de un mini-proyecto que consiste en tener una Raspberry Pi conectada a la WiFi de casa y cómo controlarla desde el exterior (el trabajo, con un móvil, …) y aprovechar sus prestaciones.

Para ello, he estado preparando un pequeño montaje que tiene un sensor de temperatura, y además es capaz de hacer un par de cosas (pequeñas) más.

Para la parte del sensor seguí el tutorial que publicó Pablo Murillo en Tutorial Arduino # 0005 – Sensor de temperatura NTC:

En este proyecto vamos a aprender a implementar un sensor de temperatura a nuestros proyectos Arduino, en este proyecto simularemos cinco estados de aviso de exceso de temperatura, sobre los cuales los cuales podríamos conectar cualquier elemento que quisiéramos que actuara llegado dicho nivel, podríamos conectar circuitos de ventilación de manera que si no consiguieran mitigar el exceso de calor llegara un punto que desconectara el sistema que estemos monitorizando, en resumen, en cualquier lugar donde un control de exceso de temperatura sea necesario.

Como no tenemos un Arduino, sino que tenemos una Raspberry Pi, necesitamos algo que sirva para hablar con los sensores analógicos. En mi caso (por cercanía física y también por simpatía con el proyecto) lo que he utilizado es el Raspberry Pi to Arduino shields connection bridge de la gente de Cooking Hacks.
Con la biblioteca que proporcionan ellos, podemos utilizar las ideas (y los programas, casi tal cual) de los proyectos de Arduino en nuestra ‘raspi’ con bastante facilidad. Sirven los mismos esquemas de conexiones y todos los documentos disponibles para esta plataforma.

Una imagen del montaje:

.

Y un vídeo de la ‘cosa’ en acción.

Se pueden ver algunos elementos más (hay un sensor de luz que se ve a la derecha del de temperatura), pero básicamente allí está el proyecto propuesto por Pablo Murillo.

Aunque el tema de los LEDs es bastante llamativo (a mi me lo parece, siendo una persona más de programas que de cacharros) lo que más me atrae del cacharrito es que se trata de un ordenador completo.
Uno no se siente a gusto si no mete internet por enmedio y algo de interacción con el aparato.

Buscando un poco por ahí encontré el proyecto SleekXMPP que nos permitiría interactuar con nuestro ordenadorcito a través del protocolo XMPP. Este protocolo nos permite comunicarnos en tiempo real y ha sido utlizado, entre otras cosas, por algunos sistemas de mensajería instantánea (por ejemplo el GTalk de Google -aunque últimamente parece que podrían estar pensando en cambiar-, Jabber, …).

En los ejemplos del proyecto hay un ‘bot’ que repite las instrucciones que le envíamos y programé una modificación para poder preguntarle a la raspi sobre la temperatura de la habitación en la que se encontraba.

Podemos ver la interacción en este vídeo:

Pero el bot de ejemplo no está pensado para este tipo de comportamiento y necesitábamos alguna arquitectura que hiciera sencillo extender el proyecto, añadirle instrucciones e interactuar remotamente (¿la meta podría ser poder añadirle instrucciones por GTalk?) así que me puse a buscar.

El amigo Javier Candeira me sugirió probar phenny, que es un bot extensible, diseñado para recibir instrucciones remotamente y con solo una diferencia con mi proyecto inicial: está pensado para ser controlado desde una canal de IRC.

El proyecto se convierte (por ahora) en un bot de IRC que es capaz de proporcionarme información del entorno de la raspi que tengo en una estantería de mi casa.

Como primer paso, modifiqué el programa que había generado a partir del tutorial de Arduteka.
En lugar de encender más o menos luces, lo que quería era saber la temperatura así que lo que hace el programita es (además de encender las luces si se dan las condiciones adecuadas), escribir en la salida estándar el mensaje:

Temperatura:

seguido de la temperatura que se haya medido.
Para eso se toman medidas 5 veces con una separación de 0.5 segundos y después se calcula la media.

El código puede verse (y descargarse) en temperatura.cpp y se compila y ejecuta de la misma forma que en el tutorial antes referido.

Para invocar remotamente este programa entra en juego phenny.
Lo primero que hay que hacer es ejecutarlo, se generará un fichero de configuración (en ~/.phenny/default.py) que hay que editar para ponerle nuestro servidor de IRC favorito, canal y algunas cosillas más y ya lo tenemos listo para funcionar.
Por defecto viene ya con un buen montón de instrucciones que podemos probar.

Pero el proyecto sigue. Queremos añadir nuestras propias instrucciones y las elegidas han sido de momento tres:

  • .temp: devuelve la temperatura que mide en ese momento el sensor instalado.
  • .ip: devuelve la ip (interna) de la raspi, que se conecta a internet a través de un router WiFi hogareño normal y corriente.
  • .ls: devuelve un listado de ficheros del directorio actual donde se haya ejecutado phenny.

El código de esta parte puede verse (y descargarse) de: testing.py y copio aquí un trozo de código de la primera de esas tres acciones para que cada uno se haga idea de lo fácil (o difícil) que es prepararlo.

def temp(phenny, input):
   arg='sudo /home/pi/usr/src/pruebas/temperatura'
   p=subprocess.Popen(arg,shell=True,stdout=subprocess.PIPE)
   data = p.communicate()
   split_data = data[0].split()
   tempC = split_data[0]
   my_ip = 'La temperatura es %s' % tempC

   phenny.say(input.nick + '!' + ' ' + my_ip)
temp.priority = 'high'
temp.commands = ['temp']

También puede verse en Código para llamar al programa que nos dice la temperatura.

Como puede verse, invocamos al programa (lo he puesto con el path completo) lanzando un proceso con Popen.
Le pedimos el resultado, lo procesamos y creamos una cadena de caracteres my_ip que luego le pasamos al bot para que la escriba en el canal de IRC.

Nótese que phenny permite asignarle un nombre (o varios) a la instrucción (temp.commands = ['temp']) y también asignarle una prioridad, que hemos puesto en los tres casos como alta (‘high’).

Una vez hecho esto podemos volver a lanzar phenny (en realidad el bot puede cargar nuevas instrucciones de manera dinámica pero eso no importa ahora) y responderá a las instrucciones que ya ‘conocía’ y a estas tres que acabamos de añadir.

Podemos verlo en acción en:

Como decía arriba, el uso del canal de IRC ha sido un hito intermedio:

El objetivo ahora tiene varias líneas de avance:

  • Modificar phenny para que también acepte instrucciones desde un cliente
    de mensajería instantánea.
  • Añadir instrucciones a phenny (¿añadirle una webcam y poder ver una foto de la habitación desde nuestro teléfono móvil?, ¿descargar un documento cuya dirección le pasemos? ¿avisarnos si la temperatura alcanza determinados umbrales? …).
  • Añadir/utilizar capacidades de administración para que algunas instrucciones sólo puedan ejecutarse por personas autorizadas

Trataremos de seguir informando.
Si alguien ve mejoras a lo que ya hay publicado (las bondades del código existente son debidas a sus respectivos autores; los defectos me corresponden a mi) o necesita alguna aclaración no tiene más que decirlo.
También si alguien conoce otros proyectos que se puedan utilizar de forma similar o de los que obtener inspiración.