Skip to content
monkeyserna edited this page Jun 3, 2014 · 38 revisions

1. Introducción

Qué es Charlie

Charlie es un robot capaz de ser controlado tanto desde una aplicación Android como desde una página web. Está construido sobre una Raspberry Pi, que junto a una placa Arduino Due se encargan de dirigir una serie de sensores, motores, leds, y otros muchos elementos, además de gestionar las conexiones de red. El robot dispone de los siguientes elementos:

  • 2 motores CC que permiten desplazar el robot.
  • 1 cámara de vídeo.
  • 1 sonar (módulo de ultrasonidos SR04).
  • 1 servomotor que controla la inclinación vertical de la cámara y el sonar.
  • 21 sensores infrarrojos CNY70 para detectar marcas en el suelo.
    • 12 de ellos realizan una lectura analógica.
    • 9 devuelven un valor booleano, indicando si la marca leída es negra o no.
  • 15 leds
    • 14 pueden ser controlados por el usuario.
    • 1 led de control.
  • 1 claxon (también llamado zumbador, piezoeléctico, o buzzer)
  • Adaptador Wi-Fi capaz de crear redes Ad Hoc (y por supuesto, conectarse a redes ya existentes).

Charlie tiene dos formas de ser controlado.

Por un lado está el modo de conducción libre, o Drive. Mediante esta modalidad de control, el robot es capaz de desplazarse y realizar una transmisión de vídeo en tiempo real. Desde una aplicación Android es posible controlar el desplazamiento del robot empleando el acelerómetro del móvil. La inclinación hacia adelante y hacia atrás del dispositivo Android determina la velocidad del robot. Las inclinaciones laterales indican el ángulo de giro del robot. En la pantalla del dispositivo Android aparece la señal de la cámara del robot en tiempo real, siendo posible controlar la inclinación vertical de la cámara mediante una barra deslizante. Por otra parte, este robot puede ser controlado desde cualquier parte del universo (que disponga de conexión a Internet) gracias a que la comunicación es realizada sobre una red Wi-Fi.

La otra posibilidad de control que ofrece el robot es el modo de programación gráfica, o Code. Consiste un lenguaje de programación gráfico (basado en el lenguaje Blockly) que permite programar el comportamiento del robot mediante bloques. Esta forma de control está disponible tanto desde la aplicación Android como desde una página web.

Un sencillo ejemplo de podría ser: avanza, y cuando detectes un objeto a 20cm o menos, detente.

Esta forma de control pretende proporcionar las herramientas necesarias para que cualquier persona, sin que tenga conocimientos específicos de programación, pueda construir comportamientos del robot, e iniciarse en el mundo de la programación mediante unas prácticas de aprendizaje basadas en la experimentación. Una gran ventaja de la programación gráfica es que no es posible cometer error sintácticos, por lo que el programador novel puede centrar todos sus esfuerzos en el aspecto lógico de la programación.

Aunque se ha dedicado mucho esfuerzo en simplificar al máximo este lenguaje, esto no lo hace menos funcional. Los programadores ya iniciados también pueden sacar un gran partido de este lenguaje, y crear comportamientos avanzados, como por ejemplo que el robot realice automáticamente un recorrido por casa siguiendo unas lineas marcadas en el suelo, mientras el programador se encuentra en otro lugar (pongamos, el Caribe) viendo la señal de la cámara y controlado que todo está en orden cuando él no se encuentra físicamente en su hogar.

Objetivos y alcance

Charlie the robot es un proyecto con gran variedad de objetivos y público a quien va destinado.

Por un lado se encuentra la interacción a distancia, ya que es posible controlar el robot a través de Internet. Esto da lugar a posibilidades que incluyen el control del robot, tanto mediante scripts como empleando el modo de conducción libre, desde cualquier lugar del universo que disponga conexión a Internet. Por tanto, un uso práctico de este robot puede ser el de poder controlar el robot a grandes distancias, y así realizar televigilancias.

Además, se ha dedicado mucho esfuerzo en hacer que este proyecto sirva como una herramienta con finalidades didácticas. Por un lado, se ha tratado de simplificar al máximo la programación del robot para que cualquier persona pueda aprender a programar. El objetivo es que cualquier persona, incluyendo niños, consiga crear comportamientos en el robot por medio de la programación de bloques gráficos. Por otro lado, los aficionados a la electrónica, informática y al mundo hágalo usted mismo (DIY, o "do it yourself" en inglés) tienen la posibilidad de reconstruir este robot, y realizar todas las modificaciones que estimen oportunas.

Quien desee reconstruir el robot tiene a su disposición de forma pública y libre toda la documentación y desarrollo creado para la concepción de este proyecto, para facilitar la construcción, y sobre todo la compresión del proyecto. Quien se aventure a reconstruir el robot disfrutará aprendiendo una gran cantidad de disciplinas relacionadas con la informática y la electrónica, entre ellas la creación de un traductor a Python, programación en multitud de lenguajes (Arduino, java, tecnologías web, python, scripting en bash), comunicaciones y redes o manejo de Linux. Tanto el código fuente desarrollado en el proyecto, como los esquemas de hardware, el diseño, la documentación y mucha más información relacionada está disponible para su uso y modificación bajo licencias libres. Este trabajo se pone a disposición de todo el interesado con la esperanza de servir de inspiración a proyectos similares.

Y por supuesto, como no todo en la vida es trabajar y emplear el tiempo en realizar actividades con una finalidad concreta, Charlie también está diseñado como un elemento de ocio, experimentación y diversión con el que pasar un buen rato, al igual que un videojuego o una serie de televisión. Siempre puedes decidir perseguir a tu mascota mediante el modo de conducción libre, o comprobar la precisión del sonar a base de colisionarlo contra un objeto (preferiblemente acolchado).

Charlie como herramienta de iniciación a la programación

Un objetivo muy interesante es el de servir de ayuda a quien desee iniciarse en la programación. Este proyecto pretende facilitar la difícil tarea de aprender a programar mediante una serie de características y funcionalidades.

Quien se inicia en el mundo de la programación debe enfrentarse a dos batallas a la vez: trasladar sus ideas a sentencias lógicas y ceñirse a una sintaxis establecida. Mediante la programación gráfica eliminamos la barrera sintáctica, ya que el código siempre será sintácticamente válido. Por tanto, la programación gráfica permite a quien se inicia en la programación focalizar sus esfuerzos en el aspecto lógico de la programación.

Muchos de estos programadores nóveles pueden encontrar desalentador el hecho de enfrentarse a una pantalla en blanco con un cursor parpadeando. ¿Por dónde se empieza? Mediante una programación gráfica basada en bloques, estos usuarios pueden navegar a través del menú de bloques, y simplemente comenzar a conectar bloques para crear comportamientos.

La programación gráfica tiene otra ventaja: es más fácil comprender el comportamiento de un algoritmo dibujado que escrito. Las acciones que se realizan en un determinado código son fácilmente distinguibles de forma visual, con bloques claramente diferenciados, apoyados por colores que indican el tipo de bloque del que se trata. Por ejemplo, los bloques matemáticos tienen un color azulado, los bucles son verdes, y las funciones asociadas al comportamiento específico del robot adoptan el color negro.

Por último, se ofrece una alternativa interesante a la forma convencional de aprender a programar: el código realizado no se ejecuta como una aplicación de texto en una terminal, sino que es capaz de interactuar con el mundo real. Es decir, en lugar de un "Hola mundo" escrito en letras blancas y fondo negro, se ofrece la posibilidad de comenzar a programar parpadeando un led, o haciendo mover al robot en círculos. El aprendiz podrá tocar el robot y ver cómo el código que acaba de realizar se ejecuta en el mundo real, en lugar de detrás de un monitor. Esto pretende servir como elemento motivacional, para sembrar el interés de los más pequeños por la programación.

Proyectos similares

En la actualidad existen multitud de proyectos robóticos, tanto robots comerciales y disponibles a la venta al público como proyectos realizados por universidades y aficionados, con aspectos y características similares a las de Charlie. A continuación se comentan dos robots que comparten similitudes con el Charlie.

Sparki Bo & Yana
Sparki es un robot de fuentes abiertas basado en Arduino, y pensado para ser utilizado por los más pequeños. Este proyecto nació de la mano de ArcBotics, a partir de una campaña de crowdfunding en Kickstarter.

Cuenta con una gran cantidad de sensores y actuadores, entre ellos sonar, brújula, fototransistores para detectar marcas en el suelo, display LCD, leds, buzzer, emisores y receptores infrarrojos, motores CC y pinzas para agarras y soltar elementos.

Sparki puede programarse de dos formas distintas: mediante código Arduino y con bloques gráficos, en un entorno de desarrollo llamado MiniBloq.

La empresa play-i lanzó una gama de robots también a través de crowdfunding, con la misión de acercar el aprendizaje de programación a todos los niveles. Estos robots pueden ser programados a través de una aplicación para iOS.

Entre estas múltiples formas de programar el comportamiento del robot, también se incluye uno basado en Blockly, lenguaje en el que está basado el modo de programación gráfica de Charlie.

Cabe destacar que estos robots no son de código libre, aunque es posible adquirir licencias de desarrollador para emplear la API y así poder construir accesorios para los robots.

http://arcbotics.com/products/sparki/ https://www.play-i.com/

2. Cómo controlar el robot

Modo de conducción libre

El modo Drive, o modo de conducción libre, es una de las dos modalidades de control del robot. Tiene las siguientes características:

  • Permite el control del desplazamiento mediante la posición física del dispositivo Android, haciendo uso del acelerómetro.
  • Reproduce la señal de la cámara del robot en tiempo real.
  • Controla la inclinación vertical de la cámara mediante una barra deslizante.

El modo drive, o modo de conducción libre, permite controlar el movimiento de los motores y del servo, además de recibir en tiempo real la señal de la cámara.

Para empezar a controlar el movimiento de los motores basta con tocar la pantalla. Un toast (mensaje emergente de Android) indica cuándo se ha establecido la conexión y sugiere tocar la pantalla para comenzar a dirigir el robot.

Cuando el usuario pulsa sobre la pantalla, el dispositivo Android comenzará a enviar los valores del acelerómetro para controlar el movimiento de los motores. La inclinación hacia adelante indicará la velocidad de avance. Al girar lateralmente el dispositivo Android, se controlará el giro del robot. Cuanto mayor sea la inclinación (tanto de avance como de giro), mayor será la velocidad con la que el robot realice la acción.

Mientras el robot está emitiendo comandos de movimiento de los motores, aparece un triángulo verde situado en la parte superior izquierda de la señal de la cámara, con la finalidad de indicar al usuario que se está realizando una transmisión. Para detener el envío de movimiento, basta con volver a tocar la pantalla. En ese momento se enviará un comando para detener los motores y se ocultará el triángulo verde.

Orientación de la cámara

A la izquierda de la pantalla hay una barra deslizante que permite controlar el ángulo vertical de la cámara por medio de un servomotor. El rango de grados de inclinación que abarca esta barra deslizante es [-35,100]. Algunos valores son los siguientes:

  • -35º Es el valor mínimo. El robot apunta hacia abajo. El desplazable de la barra se en encuentra el extremo inferior.
  • El robot apunta hacia el frente. El desplazable se encuentra ligeramente debajo del centro de su rango.
  • 90º El robot apunta hacia arriba. El desplazable se encuentra ligeramente por debajo de su extremo superior.
  • 100º El robot apunta hacia arriba y ligeramente hacia atrás. El desplazable se encuentra en su extremo superior.

Por defecto está situada en 0º sobre la horizontal. Situando el deslizante en otra posición, se enviará un comando indicando la nueva posición del servo, que moverá la plataforma que sostiene la cámara y el sonar (no empleado en este modo).

Control de motores

El resto de la pantalla lo ocupa la señal de vídeo del robot. Para comenzar a controlar el movimiento del robot, basta con tocar la pantalla, encima de la señal de vídeo. En ese momento, aparece un triángulo verde en la esquina superior derecha que indica que se están transmitiendo comandos de movimiento. Para detener el robot, basta con volver a tocar la pantalla.

El concepto subyacente detrás de este estilo de control del movimiento es el de unificar el movimiento empleado en la conducción de vehículos:

  • Para la aceleración se inclina el dispositivo hacia adelante, imitando el movimiento del pedal de un vehículo.
  • Para el giro se inclina el dispositivo hacia los lados, al igual que se giraría el volante de un coche.

Por tanto, el movimiento del robot se realiza mediante la inclinación del dispositivo móvil, con 2 variables.

La velocidad se controla con la inclinación hacia adelante y hacia atrás.

Posición velocidad mínima (detenido) y balance centrado (movimiento hacia adelante) Posición velocidad media y balance centrado (movimiento hacia adelante) Posición velocidad máxima y balance centrado (movimiento hacia adelante)

El giro se controla con la inclinación lateral del dispositivo.

Velocidad media y balance a la izquierda medio (avance a velocidad media hacia la izquierda) Velocidad media y balance a la derecha medio (avance a velocidad media hacia a derecha)

Es posible combinar ambos tipos de inclinación, para obtener cualquier tipo de movimiento.

Velocidad alta y balance a la izquierda medio (avance rápido hacia la izquierda) Velocidad alta y balance a la derecha medio (avance rápido hacia la derecha)

Esta forma de control resulta bastante intuitiva. Bastan unos minutos para aprender a controlar el robot con soltura.

Modo de programación gráfica

Esta forma de control permite exprimir todas las características del robot para que éste realice distintas acciones, ya sea leer señales en el suelo, medir distancias, o realizar secuencias de movimiento.

Este modo de programación es accesible tanto desde una página web, como desde la aplicación Android.

Programación desde Android Programación desde web

Para programar automatismos en el robot, se emplean tanto bloques ya existentes en Blockly como bloques propios.

Con respecto a la disposición de los elementos dentro de la interfaz, existen tres bloques de contenidos diferenciales:

  • Barra superior. Contiene acciones relacionadas con tratamiento de los bloques creados en el espacio de trabajo, ya sea ver el código traducido, ejecutar el scrit en el robot, o cargar/almacenar el espacio de trabajo.
    • Show code Muestra la traducción a Python del script Arduino que será enviada al robot.
    • Execute Envía el script al robot, y éste lo ejecuta.
    • Clear Elimina todos los bloques del espacio de trabajo.
    • Load Incrusta en el espacio de trabajo bloques guardados previamente.
    • Save Guarda el espacio de trabajo.
  • Barra lateral. Contiene los bloques (tanto nativos como propios) con los que realizar los scripts.
  • Espacio de trabajo,destinado a la creación de scripts o automatismos con los que dar comportamiento al robot.

¿Cómo se programa?

Para comprender cómo se programa a Charlie, se debe considerar que el paradigma de programación que se va a utilizar está basado en estados.

El robot tiene dos estados de movimiento: En movimiento (al menos un motor está girando y Detenido (ambos motores están parados). A su vez, cada uno de los leds y el claxon tienen dos estados disponibles: on (estado encendido) y off (estado apagado).

Los bloques relacionados con el movimiento de motores, control de leds y de sonar hacen cambios de estado sin realizar rupturas de flujo. Por ejemplo, la función move straight slow cambia el estado del robot a En movimiento y ejecuta inmediatamente la siguiente instrucción.

Existe un comando para detener el flujo de ejecución. Este es el bloque wait.

Este bloque espera una determinada cantidad de tiempo antes de ejecutar la siguiente instrucción.

Un paradigma alternativo sería el basado en acciones de duración definida, donde cada bloque actuaría durante un tiempo definido, por ejemplo 1 segundo. Es decir, al hacer move straight slow el robot avanzaría durante 1 segundo y después se detendría. La siguiente instrucción se ejecutaría cuando el robot hubiera completado la instrucción anterior, es decir, tras un segundo de movimiento. Como contrapartida, el robot no podría realizar ninguna acción mientras estuviese avanzando. Este modelo de programación puede resultar ligeramente más sencillo para niños y en general cualquier humano que esté iniciándose en el mundo de la programación, aunque limita las posibilidades del robot. Por otra parte, todo programa realizado mediante acciones de duración fija se puede desarrollar con el modelo actual, basado en estados. No ocurre al revés. Por ejemplo, no podríamos mover el robot y leer los sensores al mismo tiempo con el enfoque de acciones de duración determinada.

Vamos a ilustrar esto con unos ejemplos.

Recordemos que se programa como si se tratase de una máquina de estados, por lo que al ejecutar el script:

Se actualiza el estado del robot a En movimiento y comienza a avanzar indefinidamente. No se detendrá nunca.

Si el modelo fuese basado en acciones de 1 segundo, el robot se desplazaría durante 1 segundo y después se detendría. Durante el segundo que el robot está ejecutando la instrucción de movimiento no se podrá realizar ninguna otra acción (como leer algún sensor).

Veamos otro ejemplo:

¿Qué realiza esta instrucción?

Cambia el estado del robot a En movimiento e inmediatamente después actualiza el estado a Detenido. La velocidad a la que se procesan las instrucciones es mayor que la velocidad de respuesta de los motores, por lo que no se podrá apreciar ningún tipo de movimiento. Es decir, el robot cambia a En movimiento e inmediatamente a Detenido. Hay tan poco tiempo entre las instrucciones que los motores no llegan a ejecutar la primera instrucción, y el robot simplemente se detendrá (o no se moverá si ya está detenido).

En un modelo de acciones de duración fija sí que habría movimiento del robot, y el comando stop motors sería innecesario al estar implícito en la instrucción move straight slow (recordemos que pasado un cierto tiempo detiene el movimiento de los motores).

Si nuestro objetivo precisamente es avanzar durante un segundo y luego detener el movimiento, el código que realiza estas acciones es el siguiente:

  • La primera instrucción establece el estado a En movimiento.
  • La segunda instrucción espera 1 segundo. El robot permanece en el mismo estado, por lo que seguirá avanzando.
  • La tercera instrucción cambia el estado del robot a "Detenido", por lo que el robot se detiene.

Al igual que ocurre con los bloques relacionados con el movimiento de motores, los bloques de control de los leds y del claxon permiten determinar el estado de esos elementos.

El siguiente bloque cambia el estado del primer led a encendido (on):

Porque estamos programando bajo un paradigma basado en estados, este led no se apagará hasta se cambie su estado de nuevo a off. Por tanto, puede ocurrir que termine la ejecución del script en el que se encendía sin una instrucción de apagado. En tal caso, permanecerá encendido hasta que otro script incluya la instrucción set led 0 off.

Ocurre lo mismo con el claxon:

El robot estará emitiendo sonido hasta que otra instrucción cambie su estado a apagado (o se apague el robot).

Para emitir un pitido de 1 segundo de duración, por tanto, se realizaría de esta forma:

3. Comandos y comunicaciones

A continuación se explican las distintas comunicaciones que realiza el robot, ya sea para establecer un canal de comunicación entre la aplicación Android o la web y el robot, o bien para el envío de mensajes entre Arduino y Raspberry Pi.

Por un lado está la comunicación cliente-servidor entre la aplicación Android y la Raspberry, que por defecto se realiza a través de una red Wi-Fi Ad Hoc que la Raspberry Pi crea. Mediante esta conexión, el servidor de la cámara envía mediante HTTP la señal de la cámara en tiempo real, es decir, como una página web que contiene una imagen que se refresca 15 veces por segundo. A su vez, el servidor de comandos se encarga de recibir comandos y scripts realizados en el modo de programación gráfica mediante WebSocket.

Por otro lado, la comunicación entre Rasperry Pi y Arduino Due se realiza mediante comunicación en serie USB. Los comandos que el servidor recibe son enviados mediante este tipo de comunicación a la placa Arduino Due, encargada de traducir estos comandos a pulsos eléctricos que proporcionarán el comportamiento asociado a ese comando.

Cliente

Dentro del modo Drive, una vez se ha tocado la pantalla y aparece el triángulo verde en la parte superior derecha de la pantalla, se inicia el envío de comandos de movimiento.

Cada vez que el acelerómetro cambia su posición, se envía un mensaje de 3 bytes (CMD_MOVE_BALANCE <speed> <balance>) mediante WebSocket. Esto significa que existen 255 posibles valores de avance y balance.

Para el avance:

  • 0 indica velocidad mínima (detenido). El dispositivo Android está en posición vertical.
  • 255 indica velocidad máxima. El dispositivo Android está en posición horizontal.

Para el giro:

  • 0 indica balance máximo a la izquierda.
  • 123 indica avance hacia adelante
  • 255 indica balance máximo a la derecha.

Al igual que ocurre con la velocidad, cualquier valor intermedio dentro del rango [0,255] es válido y determina la intensidad del giro.

Mediante combinaciones de inclinaciones se pueden obtener distintas respuestas: desde giros de distintas velocidades sobre una rueda, hasta avances frontales, incluyendo movimientos en espiral, giros pequeños y de mayor ángulo.

Al pulsar de nuevo la pantalla se envía señal de frenado (freno por bloqueo) y se detiene el envío de movimiento. Consecuentemente, el triangulo que indica emisión dejará de estar visible.

WebSocket en Javascript

El modo de control Drive envía mediante WebSockets dos posibles comandos. Un comando asociado con el desplazamiento de avance del robot, y otro para establecer la posición del servo.

En primer lugar, crea la conexión nada más cargar la vista:

function onDeviceReady() {
    //...
    websocket = new WebSocket(WEBHOST);
    //...
}

El valor WEBHOST está definido en el archivo values.js. La dirrección por defecto es ws://192.168.0.123:800.

Las funciones que envían comandos mediante WebSocket son tres.

Cuando la barra desplazable cambia de valor, invoca a una función que envía el comando de movimiento del servo:

function moveServo(){
    var msg = new Uint8Array(2)
    msg[0] = CMD_SERVO;
    msg[1] = document.getElementById("slider").value;
    websocket.send(msg)
}

Por otra parte, cuando el dispositivo Android lee los valores del acelerómetro, llama a la función onAccelerometerChanged. Esta función envía a la Raspberry Pi el comando de movimiento con velocidad y balance, que se calculan con las coordenadas del acelerómetro. La función es la siguiente:

function onAccelerometerChanged(acceleration) {
    var msg = new Uint8Array(3);
    msg[0] = CMD_MOVE_FORWARD;
    msg[1] = map(acceleration.x * 100, 0, 1000, 255, 0,true);
    msg[2] = map(acceleration.y * 100, -1000, 1000, 0, 255,true);

    websocket.send(msg)
}

También está definida una función que detiene el desplazamiento del robot y desactiva las lecturas del acelerómetro:

function stopCharlie() {
    var msg = new Uint8Array(3)
    msg[0] = CMD_STOP;
    msg[1] = CMD_NOPARAM
    msg[2] = CMD_NOPARAM
    websocket.send(msg)

    if (watchID) {
        navigator.accelerometer.clearWatch(watchID);
        watchID = null;
    }

    isSending = false;

    document.getElementById("play").style.display = 'none';
    toast("STOPPED. Tap to start.");
}

Cuando el usuario abandona el modo Drive, se cierra la conexión:

function onBackKeyDown() {
    websocket.close();
    window.location.href = "index.html";
}

Servidor de comandos

El servidor de comandos realiza una gran parte de las funcionalidades de la Raspberry Pi. Sus tareas son:

  • Crear un servidor de WebSocket para recibir comandos y scripts desde Android.
  • Establecer una comunicación serie por USB con Arduino para enviar los distintos comandos disponibles.
  • Realizar lecturas del sonar.
  • Controlar el servo.
  • Ejecutar scripts creados en el modo de programación.
  • Indicar la ejecución de un script (proveniente del modo de programación) mediante un LED.

En cuanto al servidor de WebSocket, Python no tiene ninguna librería de creación de WebSockets por defecto. Sin embargo, sí existen multitud de librerías realizadas por la comunidad disponibles. En este caso, la librería escogida es SimpleWebSocketServer.

A continuación se expone un fragmento del fichero cmdserver.py, donde se define el servidor de WebSocket:

class WebServer(WebSocket):
	def handleMessage(self):
		# self.data contiene el mensaje recibido. Se procesa el mensaje y se realizan las acciones consecuentes.
		
	def handleConnected(self):
		print self.address, 'connected'

	def handleClose(self):
		time.sleep(2)
		servo.stop_servo(SERVOPIN)
		print self.address, 'closed'

La función handleMessage es la que define las acciones tras recibir un mensaje. Las funciones handleConneced y handleClose se desencadenan tras iniciarse o finalizarse la conexión con un cliente.

Comandos Raspberry Pi

Si el mensaje recibido es de 2 bytes se trata de un comando que debe ejecutar la Raspberry Pi. Este tipo de mensaje únicamente ocurre cuando el usuario desplaza la barra de control del servo en el modo de control Drive. Por tanto, únicamente existe un comando de este tipo, que se encarga de establecer la posición del servo.

	def handleMessage(self):
		#...
		elif len(msg) == 2:
			p1 = struct.unpack('B', msg[1])[0] * 10
			moveServo(p1)
		#...

Por tanto, lo que el servidor realiza cuando recibe este tipo de comando es convertir el valor al rango que especifica el servo y emplear la función moveServo, que también es empleada por los scripts realizados en el modo de programación gráfica.

Ejecución de scripts

La programación gráfica basa en bloques traduce el script realizado por el usuario al lenguaje en el que está escrito el servidor de comandos, para que éste lo ejecute. Por tanto, cuando el usuario decide ejecutar un script realizado mediante bloques, se traduce a Python y se envía al servidor de comandos. El servidor al recibir un script invoca a la función executeScript con el mensaje recibido, que contiene la traducción a Python que será ejecutada.

def executeScript(receivedCommand):
	print "SCRIPT RECEIVED:\n",receivedCommand
	initScript()
	print "STARTING BLOCKLY SCRIPT..."
	exec(receivedCommand)
	print "...BLOCKLY SCRIPT DONE"
	destructScript()

La función muestra una serie de comandos de control y realiza 3 acciones. En primer lugar, llama a la función que inicia los elementos conectados a los pines GPIO de la Raspberry Pi (el sónar y el servo) para activar su posible uso por parte del código recibido. El código recibido se ejecuta mediante el comando exec. Una vez finalizada la ejecución se desactivan los pines GPIO, con el objetivo principal de disminuir el consumo eléctrico del robot.

La función initScript activa los pines GPIO de la Raspberry PI y configura las entradas y salidas empleadas por el sonar y el led de control y enciende el led que indica que un script realizado mediante el bloque de programación gráfica está siendo ejecutado. Además, realiza una primera lectura del sonar, ya que en ocasiones la primera lectura devuelve valores anómalos.

def initScript():
	print "INIT GPIO..."
	GPIO.setwarnings(False)
	GPIO.setmode(GPIO.BCM)	
	GPIO.setup(LEDPIN,GPIO.OUT)	
	GPIO.setup(TRIGPIN,GPIO.OUT)
	GPIO.setup(ECHOPIN,GPIO.IN)
	GPIO.output(LEDPIN, GPIO.HIGH)
	GPIO.output(TRIGPIN, GPIO.LOW)
	readSonar() #FIRST READ IS UNSTABLE
	time.sleep(0.1)
	print "...SONAR GPIO"

Por tanto, esta función activa los pines de la Raspberry Pi y configura el pin al que está conectado el led como salida y lo enciende. Con respecto a las conexiones con el sonar, establece el pin ECHOPIN como entrada y TRIGPIN como salida. A continuación se realiza una primera lectura del sonar, cuya primera lectura es poco fiable.

Cuando el script finaliza su ejecución, se procede a apagar el led indicador, y a desactivar los pines GPIO.

def destructScript():	
	GPIO.output(LEDPIN, GPIO.LOW)
	GPIO.cleanup()	

Funciones auxiliares

Para proporcionalidad funcionalidad a los bloques propios se emplean una serie de funciones auxiliares que hacen posible la ejecución de acciones como la lectura del sonar, la posición del servo o las transmisiones con Arduino.

En el siguiente ejemplo se muestra la traducción a Python de un bloque gráfico:

Como se puede observar, para la traducción se emplean tanto funciones nativas de Python (como la función time.sleep) como propias (sendToArduino y readSonar).

Las traducciones a Python de los bloques gráficos tienen disponibles 5 funciones auxiliares.

Función Descripción
readSonar Devuelva la lectura del sonar en centímetros.
moveServo Establece la posición del servo.
sendToArduino Envía un comando a Arduino.
receiveFromArduino Envía un comando a Arduino y recibe un valor de vuelta. Esta función devuelve el valor recibido.
map Realiza un mapeado de un valor X desde [in_min,in_max] hasta [out_min,out_max].

Para realizar comunicaciones por USB, Python dispone de la librería Serial. Esta librería se importa al comienzo del código mediante la instrucción:

import serial

Para comenzar la comunicación, se indica el punto de montaje en el que se encuentra conectado el Arduino Due, y se establece la velocidad a 9600 baudios. La inicialización del servicio se realiza antes de iniciar el WebSocket:

DUE_PORT =  '/dev/ttyACM0' 
DUE_BAUDS = 9600
serialArduino = serial.Serial(DUE_PORT,DUE_BAUDS) 

Los comandos que recibe Arduino son enviados en mensajes de 3 bytes mediante la función sendToArduino:

#Enviar comando
def sendToArduino(cmd,p1,p2):
	msg = bytearray(3)
	msg[0] = struct.pack('B', int(cmd))
	msg[1] = struct.pack('B', int(p1))
	msg[2] = struct.pack('B', int(p2))
	serialArduino.write(msg)

Para recibir un comando se emplea la función readLine del objeto que gestiona la comunicación serie. El servidor únicamente espera recibir mensajes desde Arduino después de enviar un comando relacionado con la lectura de sensores. Por tanto, la función primero envía un comando y a continuación espera la respuesta y devuelve su valor:

#Recibir comando
def receiveFromArduino(cmd,p1,p2):
        sendToArduino(cmd,p1,p2)
	return serialArduino.readline()

Servidor de cámara

La cámara puede ser visualizada tanto desde un navegador web como desde la aplicación nativa.

La cámara Raspberry Pi está conectada directamente a la GPU mediante un conector FLEX de 15 pines, lo que permite procesar las imágenes a mayor velocidad haciendo uso del procesador gráfico, en lugar de la unidad principal (CPU). Esto permite comprimir las imagenes a alta velocidad, incluso obtener una calidad de imagen en full HD 1080p. Aun así, la configuración empleada para la emisión de la camara es bastante más reducida, concretamente de 320x240p a 15 imágenes por segundo.

El driver empleado para crear una interfaz que permita emitir la señal en streaming es uv4l-raspicam. Este servicio se encarga de iniciar el funcionamiento de la cámara a la resolución y tasa de refresco deseada.

Una vez iniciado el servicio uv4l, la cámara comienza a funcionar. En este momento, se crea un servidor de video en JPG a través de HTTP (sobre TCP/IP).

Para comenzar el servidor, el script general de arranque del robot delega en cam.sh (situado en la carpeta raíz del servidor) la tarea de iniciar tanto el driver como el servidor JPG.

Una vez activada la cámara, se inicia el servidor mjpg-streamer, una aplicación en linea de comandos que permite crear un servidor, para retransmitir imágenes JPG sobre una red basada en IP, desde la cámara hasta un navegador convencional. Soporta la compresión por hardware (GPU) de la cámara, lo que permite reducir drásticamente el uso de la CPU de este servidor, haciendo está aplicación un servicio ligero.

El puerto que emplea es el 8080, y la IP del robot por defecto es 192.168.0.123,por lo que para visualizar la cámara se accede a http://192.168.0.123:8080.

La web a la que se accede es la siguiente:

<html>
  <head>
    <title>CAM</title>
    <style>...</style>
  </head>
  <body>
      <img id="cam" src="/?action=stream" />
  </body>
</html>

Como se puede observar, para obtener la señal de la cámara se hace uso de la API del servidor. Concretamente se obtiene la señal de la URL /?action=stream.

Otra funcionalidad interesante puede ser la de capturar una instantánea (una fotograría). Para ello, se realizaría escribiendo la url http://192.168.0.123:8080/?action=snapshot.

Arduino

La comunicación con Arduino puede ocurrir en dos casos:

  • El modo Drive envía un mensaje WebSocket de 3 bytes.
  • La ejecución de un script escrito en el modo Code utiliza la función sendToArduio o receiveFromArduino.

Los comandos que Arduino recibe tienen siempre una longitud de 3 bytes. Estos comandos son transmitidos como mensajes de 3 bytes de longitud, donde el primer byte se corresponde con el valor del comando en el rango [0,16]. Los otros 2 bytes están reservados a posibles parámetros.

Los comandos implementados en Arduino son 17 y están numerados del 0 al 16, donde:

  • Los 11 primeros están relacionados con el movimiento de los motores (comandos 0 al 10).
  • El comando 11 está destinado al zumbador
  • El comando 12 al encendido y apagado de los leds
  • Los 4 últimos (13 al 16) realizan distintas lecturas de los sensores infrarrojos CNY70.

Para realizar la comunicación USB con la Raspberry Pi se emplea la librería Serial, disponible en el repertorio de librerías por defecto de Arduino. Por tanto, no es necesario importar esta función.

En primer lugar, en la inicialización del código se invoca a la función que da comienzo a la escucha del puerto serie a 9600 baudios:

void setup() 
{
   //...
   Serial.begin(9600);
   //...
}

El bucle principal de Arduino espera a recibir comandos de 3 bytes de longitud e invoca a la función asociada a cada comando. Para ello, en cada iteración comprueba si el buffer del puerto contiene al menos un mensaje. En caso de que sí haya un mensaje en el buffer, un switch se encarga de llamacar a la función que desencadene las acciones asociadas al comando recibido:

//Receive a message (3 bytes)
if (Serial.available() >= 3)
{
	byte cmd = Serial.read();  //Command
	byte p1  = Serial.read();  //First parameter
	byte p2  = Serial.read();  //Second parameter

	switch (cmd)
	{
		//Command 0 -> Stop
		case 0:
		stopMotors();
		break;
                //...
        }
}

Los comandos de lectura de los sensores infrarrojos envían mensajes a Raspberry Pi empleando el comando:

Serial.print(msg);

4. Blockly

Qué es Blockly

Blockly es un lengaje de programación gráfica desarrollado por Google. Está implementado en HTML, CSS y Javascript y se ejecuta sobre páginas web.

Blockly es un proyecto de código libre, por lo que todas las fuentes están disponibles para su uso y modificación. Este lenguaje todavía está en fase de desarrollo, por lo que todavía no existe una versión final. En su lugar, se ofrece el código fuente para que los desarrolladores puedan adaptar el trabajo realizado a las necesidades individuales que se deseen.

Dos de los proyectos más populares que emplean este lenguaje son el proyecto Hour of code y el MIT App Inventor.

El proyecto Hour of Code es una iniciativa sin ánimo de lucro que pretende fomentar el aprendizaje de programación mediante videojuegos basados en Blockly. El usuario debe programar mediante bloques una serie de acciones para alcanzar un objetivo y así superar el nivel.

MIT App Invertor es un proyecto realizado por la prestigiosa Universidad Massachusetts Institute of Technology que permite realizar aplicaciones para dispositivos móviles empleado bloques gráficos.

Blockly en el modo Code

El modo de programación gráfica del robot emplea el lenguaje Blockly, extendiendo su funcionalidad con bloques propios con los que poder controlar el robot.

Para ejecutar estos bloques, primero se tienen que traducir a Python, lenguaje en el que está escrito el servidor de comandos, encargado de realizar la ejecución de estos bloques. El servidor a su vez tiene definidas unas funciones auxiliares que son empleadas en algunas de las traducciones a Python de los bloques. Por ejemplo, para enviar un comando a Arduino el servidor tiene definida una función llamada sendToArduino. Esta función se emplea en la traducción de numerosos bloques.

Por tanto, para crear un bloque gráfico se deben definir dos objetos: la descripción del bloque, y la función de traducción a Python.

Descripción de un bloque gráfico

Este objeto define las propiedades de un bloque y el aspecto visual que tendrá. La sintaxis que se emplea es la siguiente:

Blockly.Blocks['nombre_bloque'] = {
    init: function() {
        // propiedades del bloque
    }
};

Es posible asociar los datos de entrada y salida a un tipo de datos. Los tipos datos disponibles son:

  • "Boolean" Booleano.
  • "Number" Valor numérico.
  • "String" Cadena.
  • "Array" Lista.

También destacar la diferencia entre tener las entradas como externas, o inline:

El bloque de la izquierda muestra la entrada "inline" (tiene establecida la propiedad `this.setInputsInline(true)), mientras que el de la derecha lo hace de forma externa.

Por último, destacar que existen principalmente dos tipos de bloques:

  • Los que devuelven datos, que tienen un conector a la izquierda del bloque.
  • Los que no devuelven datos, que tienen conectores superior e inferior, para poder establecer una secuenta.

A continuación se muestra la descripción de un bloque que devuelve un valor numérico:

Blockly.Blocks['read_sonar'] = {
    init: function () {
        this.setHelpUrl('https://github.com/monkeyserna/charlie/wiki/');
        this.appendDummyInput()
            .appendField("Read distance (cm)");
        this.setInputsInline(true);
        this.setOutput(true, "Number");
        this.setTooltip('');
    }
};

Un ejemplo de bloque propio más complejo y que no devuelve valores es move_motor:

Blockly.Blocks['move_motor'] = {
    init: function () {
        this.setHelpUrl('https://github.com/monkeyserna/charlie/wiki/');
        this.appendDummyInput()
            .appendField("Set")
            .appendField(new Blockly.FieldDropdown([
                ["left", CMD_SET_LEFT.toString()],
                ["right", CMD_SET_RIGHT.toString()],
                ["both", CMD_BOTH_MOTORS.toString() ]
            ]),
                "motor")
            .appendField("motor speed at");
        this.appendValueInput("speed")
            .setCheck("Number");
        this.appendDummyInput()
            .appendField("(0-255)");
        this.setInputsInline(true);
        this.setPreviousStatement(true);
        this.setNextStatement(true);
        this.setTooltip('');
    }
};

Función de traducción a Python

Además de la descripción del bloque, se debe implementar un objeto que contenga la función de traducción a Python.

Existen principalmente dos tipos de bloques: los que devuelven valor y los que no. La función de traducción debe tener este dato en cuenta, ya que la forma de devolver la traducción es ligeramente distinta.

La sintaxis de la función de traducción es la siguiente:

Blockly.Python['nombre_bloque'] = function (block) {

    // Operaciones necesarias para la traducción
    //El valor devuelto es distinto en función de si la traducción devuelve un valor o no

    return res; 
};

Esta función por tanto devuelve la traducción a Python del bloque nombre_bloque.

Obtener valores

Los datos de entrada de los bloques pueden definirse mediante listas desplegables o mediante entradas.

Desde lista desplegable

Para recuperar el valor de un desplegable, se emplea la siguiente función:

drop_state = block.getFieldValue('state');

En este ejemplo la función getFieldValue recupera el valor escogido en la lista con nombre state y su valor se almacena en la variable drop_state.

Desde valor de entrada

Para recuperar un valor de entrada se emplea la función valueToCode:

var value_wait = Blockly.Python.valueToCode(block, 'timeD', Blockly.Python.ORDER_ATOMIC)

En ejemplo anterior, la función obtiene el valor definido en la descripción como timeD, e indica el orden de precedencia como atómico. La información relacionada con los órdenes de precedencia está disponible en la referencia de Blockly.

Valor devuelto

La función de traducción debe devolver la traducción del bloque a lenguaje Python. Sin embargo, la sintaxis es ligeramente distinta, dependiendo de si el bloque devuelve un valor o no.

Bloque que no devuelve valor

La función de traducción debe devolver una cadena de texto con la traducción a Python de la instrucción equivalente, seguido de un salto de línea \n.

Por ejemplo, la función de traducción a Python del bloque de control de los leds obtiene dos valores, y devuelve una cadena con la función que realiza la acción correspondiente:

Blockly.Python['led'] = function (block) {
    var led = Blockly.Python.valueToCode(block, 'ledN', Blockly.Python.ORDER_ATOMIC)
    var state = block.getFieldValue('state');
    return PYT_SEND + '(' + CMD_LED + ',' + led + ',' + state + ')\n';
};

En el fichero values.js están definidas las variables:

  • PYT_SEND = "sendToArduino"
  • CMD_LED = 12;

Si suponemos que:

  • led = 0
  • state = 0

La traducción a Python del bloque led que devuelve esta función será:

"sendToArduino(12,0,0)\n"
Traducción de un bloque que sí devuelve valor

Si el bloque devuelve un valor, la función de traducción a Python debe devolver una tupla con dos elementos: la cadena que contiene la traducción a Python (¡sin saltos de linea \n!) y el orden de precedencia de este valor.

Las llamadas a función llevan la precedencia Blockly.Python.ORDER_FUNCTION_CALL. Para más información acerca de órdenes de precedencia, consulta la referencia de Blockly.

Un ejemplo: El bloque read_sonar devuelve el valor de la lectura del sonar.

Blockly.Python['read_sonar'] = function (block) {
    return [PYT_SONAR + '()', Blockly.Python.ORDER_FUNCTION_CALL];
};
  • En el fichero values.js se define PYT_SONAR = "readSonar";.
  • Por otra parte, el orden de precedencia de las funciones se define como Blockly.Python.ORDER_FUNCTION_CALL = 2;

Por tanto, el valor devuelto por esta función de traducción será la tupa:

["readSonar()",2]

Añadir palabras reservadas

Con el objetivo de prevenir colisiones entre la ejecución de los scripts realizados en en el modo de programación gráfico y el código Python del servidor, Blockly permite declarar una serie palabras reservadas que impiden que una función de traducción declare una variable o función con un nombre que ya está siendo empleado por el código del servidor, o bien es una función nativa del lenguaje Python.

Por ejemplo, algunos bloques emplean la función auxiliar sendToArduino para enviar comandos a Arduino. Si definimos mediante bloques gráficos una función con el mismo nombre (es decir, sendToArduino), cuando se realice la traducción a Python, Blockly renombrará esta función añadiéndole un sufijo numérico que prevendrá la sobreescritura de la función original.

Si no hubiésemos incluido la palabra reservada sendToArduino, la función original sería sobreescrita.

Por tanto, mediante esta instrucción se definen todas las palabras reservadas:

//Add reserved word to prevent collisions with python server
Blockly.Python.addReservedWords("logging,SERVER_IP,WS_PORT,LEDPIN,SERVOPIN,ECHOPIN,TRIGPIN,SONARTIMEOUT,DUE_PORT,DUE_BAUDS,initSonar,readSonar,destructSonar,moveServo,sendToArduino,receiveFromArduino,executeScript,map,WebServer,WebSocket,self,parser,OptionParser,options,args,SimpleSSLWebSocketServer,SimpleWebSocketServer,close_sig_handler,server,sys,servo,serialArduino,signal");

Futuras ampliaciones

El robot ha sido diseñado de forma que sea polivante, de forma que realizar ampliaciones resulte los más sencillo posible. Las posibilidades son inmensas. Hay disponibles una gran cantidad de pines para realizar futuras ampliaciones. Por otra parte, también hay espacio suficiente en memoria y la carga de trabajo de los procesadores de la Raspberry Pi y del Arduino Due son actualmente relativamente bajas. Esto permite realizar nuevas implementaciones software. Algunas opciones para completar el proyecto son las siguientes:

  • Incluir bloques de control de la cámara.
  • Simplificar la programación de los bloques.
  • Realizar una interfaz más intuitiva.
  • Migrar la aplicación Android a otros sistemas operativos: iOS, Windows Phone, BlackberryOS, ...
  • Mejorar la estética del robot.
    • Crear una carcasa.
  • Simplificar la configuración de redes.
  • Mejorar documentación.
    • Crear un amplio repertorio de ejemplos.

Por otra parte, cualquier persona interesada puede realizar modificaciones en el proyecto, de forma es perfectamente posible añadir nuevos elementos hardware al robot, por ejemplo un acelerómetro, brújula, pinzas para agarras y soltar elementos, display, altavoces, o sustituir la fuente de alimentación por una batería de Li-Po solar.

Sobre implementaciones software, algunas ideas que pueden resultar muy interesates para aumentar las posibilidades del robot son las siguientes:

  • Grabar (y reproducir) recorridos realizados en el modo de conducción libre
  • Posibilidad de controlar 2 robots desde un único cliente, y que los robot puedan adoptar tanto comportamientos tanto iguales como distintos.
  • Dotar al robot con funciones de reconocimiento de voz (incorporando un micrófono).
  • Incepción de un nuevo modo de control que emplee visión artificial.
  • Creación de un nuevo modo de control basado en acciones de duración definida, de forma que niños de 5+ años puedan crear comportamientos en el robot.

Resultado final

Este proyecto ha sido posible gracias a una gran cantidad de esfuerzo dedicado a lo largo de todo el curso académico, desde septiembre hasta mayo. Afortunadamente, la evolución del proyecto ha sido constante. Todos los hitos se han conseguido realizar en el tiempo establecido y se han logrado soluciones exitosas para todos los contratiempos que han surgido.

El planteamiento inicial consistía en controlar el desplazamiento de un robot mediante una aplicación Android y, a su vez, poder ver en la pantalla del dispositivo Android la señal de la cámara del robot en tiempo real. La forma de controlar el robot estaría basada en la posición física del dispositivo Android: haciendo uso de su acelerómetro se podría controlar la velocidad y el ángulo de giro a la vez. La conexión entre el robot y el dispositivo cliente se realizaría por Wi-Fi, lo que haría posible el control de robot a grandes distancias a través de Internet.

A nivel de hardware, se especificó que se emplearía al menos los siguientes elementos: Raspberry Pi, Arduino Due, GoShield-GR, cámara y adaptador de red inalámbrica. Por un lado, la Raspberry Pi cumpliría tres funciones principales: conectarse a una red mediante Wi-Fi, recibir los comandos emitidos desde la aplicación Android y trasladarlos a Arduino, y enviar la señal de la cámara a la aplicación Android en tiempo real. La placa Arduino Due sería la encargada de recibir los comandos enviados por la Raspberry Pi y convertirlos en señales de control de los motores situados en la placa GoShield-GR.

Se han logrado cumplir todos los objetivos propuestos en el comienzo del proyecto. El planteamiento inicial contemplaba una parte de las características de lo que finalmente se ha llamado modo de control Drive. A esta modalidad se le ha añadido además la posibilidad de controlar la inclinación vertical por medio de un servomotor.

Quizás el mayor esfuerzo ha sido lograr crear un modo de control del robot adicional: un lenguaje de programación gráfico con el que programar comportamientos en el robot. Para ello, se ha implementado en JavaScript un traductor de bloques gráficos a Python. Estas traducciones son enviadas mediante WebSocket al servidor de la Raspberry Pi, escrito también en Python, que se encarga (en otras muchas tareas) de convertir lo que en un principio eran bloques gráficos a interacción real del robot.

A nivel de hardware, el robot final es mucho más completo que el planteado en los inicios. Se han incorporado piezas 3D y distintos módulos, como el servomotor y el sonar, que aumentan sustancialmente las posibilidades de este robot.

Este proyecto ha reforzado muchos de los conocimientos adquiridos a lo largo de la carrera. Se han abarcado una gran cantidad de ramas de conocimiento relacionadas con la informática y la electrónica. Algunos de estos campos son:

  • Robótica
  • Electrónica y diseño de circuitos
  • Procesamiento de lenguajes
  • Desarrollo software (Arduino, Python, Android, PhoneGap, tecnologías web, Bash)
  • Manejo de Linux
  • Comunicaciones y redes
  • Diseño de piezas 3D

El resultado: un robot llamado Charlie.

Clone this wiki locally