viernes, 30 de marzo de 2012

Software básico del robot (Parte III)


En esta entrada completamos la descripción del software del robot: veremos qué comandos puede interpretar, cómo se divide su inteligencia en comportamientos, qué acciones ejecuta al cumplirse la condición de inicio de cada uno de ellos y cómo se establecen sus prioridades.

Actualmente hemos implementado 6 comportamientos distintos que, ordenados de mayor a menor prioridad son: Stop(), MotorSpeed(), Monitoring(), Shoot(), Mapping() y ProcessData(). Como ya sabemos, en esta arquitectura subsumption podríamos añadir o eliminar behaviors en cualquier momento sin afectar al resto.

Desde una aplicación Android muy sencilla en la que disponemos de botones que nos permiten elegir la acción que debe realizar el robot, enviamos por Bluetooth unos bytes de comando. Como tenemos 6 behaviours, necesitamos 3 bits para informar al robot de qué comportamiento debe tomar el control: serán los 3 bits más significativos del primer byte que se envía (c2, c1 y c0).

Con este protocolo de comunicación, el método takeControl() de todos los behaviours desplazará 5 posiciones a la derecha el byte recibido y comprobará si c2, c1 y c0 coinciden con el código de comando que se le ha asignado a ese comportamiento en concreto.

A continuación pasamos a explicar con detalle qué realiza el método action() en cada uno de los behaviours, empezando por los que tienen una mayor prioridad:

  1. Stop() (c2 c1 c0 = 000): Detiene el robot en caso de emergencia, parando los 3 motores que tiene y vaciando la pila de comandos pendientes.

  2. MotorSpeed() (c2 c1 c0 = 001): Se encarga de controlar la velocidad de los motores. En este caso no nos basta con mandar por Bluetooth un único byte que determine qué comando se ha de ejecutar, sino que necesitamos 2 bytes adicionales: el segundo indica la velocidad del motor izquierdo (motor A) y el tercero la del motor derecho (motor B). Como en Java tenemos bytes con signo, en complemento a 2, el valor de velocidad que enviamos por Bluetooth estará comprendido entre -128 y 127. El signo indica si el motor tiene que girar en sentido de avance o retroceso. El valor absoluto indicará el módulo de la velocidad, multiplicándolo previamente por un factor: la velocidad del robot puede variar entre 0 y 900, pero sólo dejaremos que alcance 700 para tener un margen de seguridad; por lo tanto, para calcular la velocidad final tendremos que multiplicar por un factor de 700/128= 5,46.

  3. Monitoring() (c2 c1 c0 = 110): Nos permite monitorizar el estado de los sensores. Comprueba la distancia a la que se encuentra un objeto del sensor de ultrasonidos, de qué color es la siguiente pelota a disparar y de qué color es el contenedor que tiene delante (en caso de que haya encontrado alguno). Estos datos se mandan por un stream de salida, por Bluetooth.

  4. Shoot() (c2 c1 c0 = 011): Simplemente dispara una pelota, haciendo que el motor B gire 360º a su máxima velocidad.

  5. Mapping() (c2 c1 c0 = 010): Este comportamiento ha sido modificado varias veces para buscar el mejor algoritmo de clasificación de residuos, por lo que explicaremos cómo funciona en su última actualización. Cuando toma el control, hace que el robot dé unas vueltas de reconocimiento para detectar los contenedores que hay en su entorno y poder clasificar los residuos en su correspondiente contenedor. Aquí podemos ver su diagrama de estados actual, con una explicación de qué ocurre en cada estado:




    - Detectando contenedor: Cuando empieza a detectar un objeto que se encuentra dentro del rango de distancias establecido, almacena en las variables temporales “disttemp” y “tachotemp” a qué distancia y a qué ángulo respecto del de partida lo ha detectado por primera vez. La variable booleana “detectado” se pondrá a “true”.

    -
    Comprobando color: Teniendo en cuenta el efecto provocado por la apertura del haz de ultrasonidos, sabemos que cada contenedor será detectado a lo largo de un determinado arco de circunferencia. Para poder determinar el punto medio del contenedor, necesitamos hacer la media entre el ángulo inicial de este sector circular (almacenado en “tachotemp”) y el ángulo en el que deja de detectar el contenedor (se obtiene como Motor.A.getTachoCount()). Al dejar de detectar un contenedor, hacemos que el robot rote en sentido contrario un ángulo “retroceder” (int retroceder = (Motor.A.getTachoCount() + 50 - tachotemp) / 2;). Con esto pretendemos que quede apuntando al punto medio del contenedor. Experimentalmente vimos que era necesario aplicar una corrección; por este motivo, aparece 50 sumando en la expresión de “retroceder”.
    Cuando ya tenemos al robot centrado respecto al contenedor, avanza hacia él una distancia “dist-10” y el sensor de color toma una lectura. Si el color del contenedor y el de la pelota del cargador coinciden, se pone la variable “dispara” a “true”.
    Finalmente, el robot retrocede la distancia que había avanzado para acercarse al contenedor.

    -
    Disparar pelota: Si, al volver al centro del escenario tras acercarse a un contenedor, el robot tiene “dispara==true”, accionará el motor B para disparar una pelota al contenedor.

    -
    Parada: Entramos en este estado cuando la variable “tachoTotal” se hace cero, lo cual quiere decir que el robot ya ha dado 4 vueltas completas en el escenario.

    -Buscar contenedor: El robot está programado para dar 4 vueltas de 360º en busca de contenedores a partir de un ángulo inicial de referencia. Los grados que quedan para terminar el mapeo se almacenan en la variable “tachoTotal”. Mientras el sensor de ultrasonidos no detecte ningún objeto a un radio menor que el establecido (actualmente, 70 cm), el robot seguirá rotando a una velocidad de motores constante (200). La variable donde se almacena la lectura del sensor de ultrasonidos es “dist”.
  6. ProccesData() (c2 c1 c0 = 100): Ahora mismo este comportamiento no se está utilizando. Si conseguimos mejorar la detección de contenedores, lo usaremos para que, habiendo almacenado previamente la posición y el color de todos los contenedores del escenario, luego el robot vaya clasificando las pelotas.

También hay que señalar que, de momento, no hemos necesitado utilizar los métodos suppress() de ninguno de los behaviours.

No podemos olvidar que, para establecer esta arquitectura subsumption, necesitamos crear un árbitro que gestione los comportamientos. Esto se realiza en la clase main.java, con las siguientes líneas de código:

Behavior[] behaviours = { new ProcessData(), new Mapping(), new Shoot(), new Monitoring(), new MotorSpeed(), new Stop() }; // Crea los behaviours, ordenados de menor a mayor prioridad

ar = new Arbitrator(behaviours); // Inicializa el arbitro con los behaviours correspondientes

ar.start(); // Lanza el arbitro

Aunque con estos comportamientos descritos el robot ya es capaz de realizar todas las funciones básicas planteadas para el hito 1, estamos estudiando la posibilidad de incorporar un nuevo sensor I2C que nos permita mejorar la detección de contenedores. Si finalmente decidimos integrarlo, tendremos que modificar el software que aquí hemos explicado.


jueves, 29 de marzo de 2012

Software básico del robot (Parte II)

En esta entrada seguiremos hablando sobre el software del robot. Antes de describir con detalle las partes del código que todavía no hemos explicado, lo cual haremos en la siguiente entrada, es necesario entender cómo es una arquitectura subsumption para la programación de robots.

¿QUÉ ES LA ARQUITECTURA SUBSUMPTION?

La parte de la lógica del robot que establece las diferentes acciones que puede ejecutar está implementada mediante una arquitectura subsumption. Esto quiere decir que, para reducir la complejidad de su inteligencia hemos creado pequeños módulos de comportamiento (que llamaremos “behaviours”) muy simples.

Toda arquitectura subsumption tiene dos componentes fundamentales:

  • Comportamientos (behaviours): Cada una de las tareas u objetivos que forman el algoritmo de control del robot debe ser separado en un módulo distinto. Todos estos módulos deben tener una condición de inicio (algo que se debe satisfacer para que el comportamiento tome el control del robot), una acción a ejecutar en el momento de tomar el control y otra acción a ejecutar cuando se termina de ejecutar el comportamiento del robot (por ejemplo, cambiar el estado de algún indicador). Además, es necesario que estos comportamientos tengan una escala de prioridad para saber cuáles son más relevantes que otros.

  • Árbitro: Es el encargado de iniciar la ejecución de los comportamientos y dar el control al comportamiento correspondiente si se cumple su condición de inicio. Si en el mismo instante se cumplen las condiciones de inicio de varios módulos, él sólo permitirá que el de mayor prioridad tenga el control.

Esta arquitectura ofrece grandes ventajas a la hora de programar un robot: se pueden añadir nuevos comportamientos o eliminar alguno de los existentes sin afectar al resto del código y es muy fácil de entender, por el hecho de haber dividido el algoritmo en bloques sencillos.

¿CÓMO PODEMOS IMPLEMENTAR ESTA ARQUITECTURA?

El entorno de desarrollo LeJOS nos ofrece la librería Subsumption, con todas las herramientas necesarias para crear y gestionar comportamientos:

  • Interfaz Behavior. Nos permite crear cada módulo de comportamiento y definir todas sus características a través de tres métodos abstractos:
    -boolean takeControl(): En el momento en el que devuelve un valor verdadero, el árbitro le cede el control del robot a este comportamiento (siempre y cuando no se cumpla la condición de inicio de otro comportamiento de mayor prioridad).
    -void action(): Contiene la tarea a ejecutar por el robot cuando este comportamiento se vuelve activo.
    -void suppress(): Es el método que se ejecuta en cuanto se termina la ejecución del action(). Debe ser un método corto; generalmente se usa para cambiar el valor de algún flag.
  • Clase Arbitrator. Es la clase encargada de gestionar los comportamientos según su importancia y dar el control a cada uno cuando se cumplen las condiciones de inicio. Para construir un objeto de esta clase, es necesario pasarle como argumento un array de Behaviors ordenados de menor a mayor prioridad. Una vez que se lanza el árbitro con su método Public void start(), éste llama al método takeControl() de cada Behavior, empezando por el de mayor prioridad, hasta que encuentra alguno que cumple la condición de inicio. En ese momento, le cede el control al comportamiento correspondiente para que ejecute su método action() una única vez.

Con todos estos conceptos teóricos y la librería Subsumption, ya tenemos los elementos necesarios para diseñar e implementar los comportamientos de nuestro robot. Al igual que nos ocurre a los seres humanos, ciertas acciones tendrán más prioridad que otras. Por ejemplo, si una persona o un robot decide andar y en su camino encuentra un obstáculo, deberá tener más prioridad el comportamiento de “evitar obstáculos” que el de “andar”. Esta filosofía será la que aplicaremos a la hora de crear el array de comportamientos de Kigo.

lunes, 26 de marzo de 2012

Hito 1: Vídeo de demostración

A continuación presentamos el primer vídeo de muestra de nuestro robot en funcionamiento.

El escenario donde tiene lugar es un tablero liso sobre el que se han colocado contenedores de distintos colores. El robot debe dar una vuelta de reconocimiento y, mediante su sensor de ultrasonidos, detectar qué contenedores hay a menos de 70 cm de él. Cuando detecta un contenedor, debe acercarse a él para ver de qué color es (el sensor de color necesita estar a una distancia de unos 2 cm para detectar bien). Si el color del contenedor coincide con el de la pelota que está preparada para ser disparada, el robot introducirá el residuo en el contenedor. En caso de que no haya coincidencia, seguirá buscando nuevos contenedores.

En el vídeo se pueden ver los principales problemas que hemos encontrado durante la consecución de los objetivos del primer hito. El que más nos preocupa es la imprecisión del sensor de ultrasonidos, ya que la apertura del haz hace que haya una gran imprecisión a la hora de detectar la posición de un contenedor e incluso detecte un mismo contenedor varias veces. Próximamente investigaremos para solucionarlo.

Software básico del robot (Parte I)


Como ya explicamos en la primera entrada del blog, el software de nuestro proyecto se divide en 3 partes fundamentales: el del robot, el de la aplicación Android y el software intermediario para Kinect.

Hoy presentamos una parte de la estructura básica del software del robot: el establecimiento de las conexiones Bluetooth con otros dispositivos y la gestión de la pila de los comandos que recibe de ellos. En la siguiente entrada, veremos cuáles son estos comandos que el robot puede ejecutar y cómo estructura sus prioridades.

Todo el software está programado en Java, utilizando como herramienta de desarrollo el Eclipse con el plugin de la plataforma leJOS. LeJOS es un proyecto open source: un firmware que reemplaza al original de Lego y nos permite programar el robot en Java. Incluye su propia JVM (Java Virtual Machine) y su propia API (Application Programming Interface) con muchas clases que pueden ser implementadas por los programadores para tener una mayor funcionalidad.

En el siguiente diagrama de bloques podemos ver cuáles son las clases que forman el software del robot y cómo se relacionan entre ellas:

A continuación pasamos a explicar con más detalle las clases marcadas en rojo en el diagrama:

  1. Main.java

    Esta clase tiene dos objetivos principales: el primero de ellos es lanzar el hilo encargado de las comunicaciones Bluetooth; el segundo, lanzar un árbitro que se encargue de controlar el comportamiento del robot en función de unos niveles de prioridad. Este último aspecto mencionado será explicado con detalle en la próxima entrada del blog.

  2. DataExchange.java

    La misión de esta clase es actuar como nexo de unión entre todos los comportamientos que puede tener el robot. Por un lado, almacena toda la información que han de compartir, como el estado de los sensores; por otro, se encarga de gestionar la pila de los comandos pendientes del robot.

    Las variables de esta clase son:

    · Radio de acción del robot (distancia máxima a la que puede encontrarse un contenedor para ser detectado)

    · Distancia de seguridad (margen que deja el robot al acercarse a un contenedor para detectar su color)

    · Streams de datos de entrada y de salida (se importan las clases java.io.DataInputStream y java.io.DataOutputStream)

    · Boolean “conectado” (se pone a “false” para cerrar la conexión Bluetooth)

    · Pila de comandos pendientes de ser ejecutados

    · Listado de contenedores detectados hasta el momento

    · Factor de giro para las rotaciones de los motores

    · Lectura del sensor de ultrasonidos

    · Lecturas de los sensores de color: tanto el que apunta a los contenedores como el que detecta el color de los residuos.

    Los métodos que encontramos en esta clase son:

    · Getters y setters para leer o modificar los valores de las variables. De todos ellos, la única cuestión de interés es que para almacenar, leer o borrar un comando de la pila, es necesario usar el modificador “synchronized” para evitar accesos simultáneos en la pila.

    · public static void avanza(int dist, boolean reverse): hace que el robot avance (reverse == false) o retroceda (reverse == true) la distancia en centímetros pasada como parámetro. Para que los motores muevan las ruedas, hay que expresar esta distancia en forma de ángulo que queremos que giren. Como cada rueda tiene un diámetro de 4,32 cm, el ángulo que tendrá que girar para avanzar una distancia “dist” y dejar un margen de seguridad “DIST_SEG” para no chocarse será: int rotar = (int) (360 * ((dist - DIST_SEG) / (4.32 * Math.PI)));


  3. Container.java

    Un objeto de la clase Container representa un contenedor donde se pueden almacenar los residuos. Cada vez que el robot detecte un nuevo contenedor, se creará un objeto de esta clase. Las variables de esta clase son:

    · Ángulo: ángulo que tiene que girar el robot desde su posición inicial para estar orientado hacia ese contenedor.

    · Distancia: una vez que el robot se orienta hacia el contenedor, distancia de él a la que se encuentra.

    · Color: color del contenedor.

  4. BTConnection.java

    Es un hilo que se ejecuta en paralelo con el resto del programa y se encarga de la comunicación Bluetooth: espera una nueva conexión y, cuando la tiene, recibe bytes de comandos y datos de 4 en 4. Esto se realiza a través de dos métodos:

    · waitForConnection()
    Es el método encargado de esperar a una conexión entrante y, cuando la encuentra, abrir los streams de datos de entrada y de salida, tal y como se ve en el siguiente diagrama de estados:

    · run()
    Mientras haya una conexión activa, este método se encargará de leer los bytes de entrada de 4 en 4. El pseudocódigo de este método es:

    while(DataExchange.isConnected()) {
    if(“Hay datos del DataInputStream”) {
    //Añadirlos a la pila de comandos }
    }
    //Cerrar el DataInputStream
    //Cerrar el DataOutputStream
    //Cerrar la NXTConnection



lunes, 19 de marzo de 2012

Documentación Hardware


Para conocer mejor las herramientas de las que disponemos para realizar nuestra práctica, hemos estado investigando sobre el hardware que nos proporciona el kit Lego Mindstorms.

Con toda la información que hemos encontrado, hemos elaborado nuestra propia documentación con los aspectos más importantes del hardware. En este link se puede leer el archivo.

Se puede resumir el hardware del bloque NXT con el siguiente diagrama:


Una de las características que nos ha llamado la atención es la posibilidad de instalar sensores con interfaz I2C que mejoren las funcionalidades de nuestro robot. Para el segundo hito seguiremos indagando sobre el tema e intentaremos adaptar un sensor de este tipo para nuestro proyecto.

jueves, 15 de marzo de 2012

Planificación de hitos

En la última reunión con el tutor de nuestro proyecto, Juan Manuel Montero, hemos planteado los siguientes objetivos para cada uno de los hitos de la práctica:

Hito 1:

  • Implementación de la inteligencia del robot para localizar contenedores.
  • Implementación de la clasificación de residuos según su color.
  • Primer boceto de la aplicación Android.
  • Establecimiento de la comunicación Bluetooth entre el robot y el dispositivo móvil.
  • Control remoto del movimiento del robot desde Android.
  • Primera monitorización de sensores desde Android.

Hito 2:

  • Mejora de la interfaz de la aplicación Android.
  • Monitorización avanzada de sensores desde Android.
  • Primera toma de contacto con Kinect y Wiimote.
  • Evaluación de la integración de un sensor I2C.

Hito 3:

  • Integración completa del sensor I2C.
  • Control del robot a través de Wiimote.
  • Control del robot a través de Kinect.

jueves, 1 de marzo de 2012

Comenzamos nuestro proyecto

La creación de este blog tiene la finalidad de dar a conocer cada uno de los pasos en el desarrollo de nuestra práctica innovadora para el Laboratorio de Sistemas Electrónicos Digitales de la ETSIT (UPM) durante el curso 2011/2012.

Como bien indica el título del blog, el proyecto que vamos a realizar tiene como nombre: "Kigo: el robot clasificador de residuos". Al finalizar el mismo, el robot con el que estamos trabajando tendrá la capacidad de clasificar pelotas de distintos colores en sus correspondientes contenedores de manera autónoma. Además, seremos capaces de con
trolar el movimiento de avance, retroceso, giro, parada y disparo de este robot mediante determinados gestos y a través de un dispositivo Android.

El hardware del que partimos es, por un lado, un robot Lego Mindstorm basado en un bloque NXT, el cual dota de inteligencia y conectividad Bluetooth al robot. Por otro, utilizaremos un dispositivo Android, así como el hardware de los sistemas Kinect y Wiimote.

El software que desarrollaremos se divide en tres bloques principales:
  1. Software del robot: hace posible que el robot tenga toda la lógica de clasificación de residuos y sepa entender los comandos que recibe por Bluetooth del dispositivo Android o de un ordenador.
  2. Aplicación Android: permite al usuario controlar al robot de manera inalámbrica.
  3. Software intermediario para Kinect: el reconocimiento de gestos con Kinect lo realizaremos a través de un ordenador.
Las comunicaciones entre todos los bloques que forman este proyecto se realizará mediante conexión Bluetooth, con un protocolo simplificado.

Esperamos que esta práctica resulte interesante para todo el que lea estas líneas y de ella surjan aportaciones positivas sobre el estudio del reconocimiento de gestos.