En este artículo voy presentar una mejora del programa que ya puse en el artículo Control remoto con LeJOS de un Kart NXT. Lo que se plantea en este artículo es conseguir un Kart NXT radio-controlado mediante otro NXT (un mando) que funcione eficientemente (tenga retardos factibles, y sea manejable). El de este artículo fue el modelo final que se usó en la competición de Karts del domingo, y el encargado de llevar la cámara (un Iphone 3G).
Control remoto con LeJOS 2 de un Kart NXT
El problema principal radica en conseguir un modelo radiocontrol en NXT que pueda competir contra Karts creados con motores y componentes Power Function. En primer lugar estos Karts pesarán menos de base, debido al menor peso de sus motores, y a que no necesitan un ladrillo NXT. También tienen un manejo mucho más sencillo, debido a su sistema de control mediante mando de infrarrojos. La única ventaja con la que partimos es que la comunicación Bluetooth tiene un mayor alcance y además no se ve influida por la luz solar, la cuál interfiere mucho en las comunicaciones mediante infrarrojos.
El problema de la latencia:
Uno de los problemas que tenía el programa anterior era el de las latencias. Debido a la necesidad de enviar suficientemente lento para que el receptor sea capaz de procesar los datos, necesitamos añadir un retardo en el emisor. Este retardo produce, entre otras cosas, que haya una latencia notable entre que nosotros movemos el mando y el kart actúa en consecuencia. Hay varias formas de mitigar esto:
– Reducir el tiempo que se tardan en procesar los datos.
– Ajustar al máximo el retardo a ese tiempo de procesamiento.
– Enviar datos cuando sea estrictamente necesario.
En primer lugar es muy importante que reduzcamos el tiempo que se tardan en procesar los datos. Por ejemplo en el programa del artículo anterior tardábamos mucho en procesar el giro ya que el método rotateTo de la clase Navigator no devuelve el control hasta que el motor haya girado al ángulo determinado. Para eliminar todo este tiempo lo que hacemos es usar la función rotateTo que devuelve el control (ver código del Kart, línea 55). De este modo podremos seguir recibiendo datos tranquilamente mientras rota, y si recibe otra orden de girar, lo que hacemos es parar los motores, y volver a ejecutar el rotateTo con el nuevo grado objetivo y en modo devolver control.
Otra cosa que ayuda a reducir el tiempo de procesamiento en el Kart es procesar la mayor parte posible de los datos en el mando antes de enviarlos, de manera que solo hagamos en el Kart lo que no podríamos hacer en el mando.
Ajustar el retardo en función de el tiempo de procesamiento es una tarea complicada, ya que físicamente es imposible medirlo, por lo que se consigue mediante prueba de ensayo y error.
El último punto es muy importante. Si enviamos datos irrelevantes, y el Kart gasta tiempo procesandolos, perdemos ese tiempo que podríamos estar usandolo para algo realmente necesario o importante. Por tanto el programa del mando se tiene que encargar de enviar información cuando sea estrictamente necesario. Por ejemplo ya habíamos visto que el hecho de enviar constantemente información de que uno de los tacómetros del mando marca 0 grados no tiene sentido. Se ha de mandar información cuando haya cambios relevantes en el mando, como que hallamos movido un motor más de X grados. Por supuesto esto puede crear imprecisión en el mando, por lo que debemos tener en cuenta en que casos nos interesa mayor fineza a la hora de manejar el robot, y que otros casos podemos prescindir de contar con tanta precisión.
En el programa del mando podéis ver como se envía información del giro con mayor o menor frecuencia dependiendo del grado al que queramos rotar (líneas 61 a 81).
Ajustando valores:
Un factor a tener en cuenta es la relación entre cuantos grados giremos los motores de nuestro mando, y cuanta aceleración o giro vamos a dar al Kart. El caso de girar la dirección es especialmente delicado, ya que necesitaremos máxima precisión para poder conducir sin chocarnos o salir constantemente de la pista. En este caso además tenemos que contar con que la dirección no puede girar más de un número determinado de grados o podría romperse. Por eso buscamos un límite de grados, y lo hacemos corresponder con el máximo de grados que queremos girar el volante de nuestro mandos.
Debido a las marchas reductoras la conversión entre giro de mando y de dirección no es exacta, pero lo importante es que el mando sea cómodo. Por eso un limite de 90 grados al girar el mando ya debería dar como resultado el giro máximo de la suspensión, y en un giro de entre 1 y 45 grados deberíamos poder manejar al kart para que realice la mayoría de curvas del circuito.
En el programa se puede ver como tenemos una mayor precisión para los giros más cortos (de la linea 61 a la 65), y está precisión va disminuyendo progresivamente cuanto más giremos el mando (líneas 66 a 81).
Por supuesto este escalado de precisión se debe aplicar también a la velocidad, como se ve en la línea 90, donde aplicamos un cubo para el cálculo de velocidad, lo que hará que los primeros grados tengan un control preciso de velocidades bajas, mientras que cuando giremos mucho el control el coche saldrá disparado.
Un mando manejable:
La forma física del mando es importante también. El kart se hacía muy difícil de manejar con el joystick, así que creamos un nuevo mando con el siguiente aspecto:
Como véis cuenta con un gatillo para acelerar, un pequeño volante para girar, y un botón de freno que usaremos en casos de emergencia. Es un modelo mejorable, pero es mucho más manejable. Uno de los problemas principales es su peso, pero éste es inherente a su naturaleza NXT.
Programa final:
El código a sido bastante modificado con respecto a del artículo anterior. Se han tratado de incluir las mejoras comentadas en los apartados anteriores, para mejorar sobretodo el control. Respecto a la parte física se ha cambiado la transmisión.
En conjunto se ha conseguido un retardo muy bajo de respuesta (entre 150 y 200 ms aprox), muy similar al que tienen los mandos de infrarrojos, así que en este apartado se ha conseguido alcanzar a Power Functions. El manejo es mucho más complejo, ya que nos permite girar los grados que queramos la dirección, y no un giro fijo como el que se consigue con el sistema de gomas usado por los otros karts en la competición. Respecto al peso sigue estando en desventaja, y es imposible mejorar este aspecto. Además al ser el coche cámara de la competición lo que aumenta su peso en más de 150 gramos, haciendo que fuera el único kart de la competición en superar el kilogramo de peso.
Aquí os dejo el código del mando:
import lejos.nxt.Button; import lejos.nxt.LCD; import lejos.nxt.Motor; import lejos.nxt.SensorPort; import lejos.nxt.TouchSensor; import lejos.nxt.comm.BTConnection; import lejos.nxt.comm.Bluetooth; import java.io.*; import javax.bluetooth.*; import java.lang.Math; public class Mando { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String nombre = "NXT1"; LCD.drawString("Conectando...", 2, 1); TouchSensor contacto = new TouchSensor(SensorPort.S1); LCD.refresh(); int ant_gir = 0; int ant = 0; RemoteDevice bt = Bluetooth.getKnownDevice(nombre); if (bt == null){ LCD.clear(); LCD.drawString("No existe ese dispositivo", 0, 1); Thread.sleep(2000); System.exit(1); } BTConnection btc = Bluetooth.connect(bt); if (btc == null){ LCD.clear(); LCD.drawString("Conexión fallida", 1, 1); Thread.sleep(2000); System.exit(1); } LCD.clear(); LCD.drawString("Conectado", 2, 1); Thread.sleep(2000); DataOutputStream dos = btc.openDataOutputStream(); LCD.clear(); LCD.drawString("Mando en funcionamiento", 2, 1); while(!Button.ESCAPE.isPressed()){ Thread.sleep(50); //Giro int i = Motor.B.getTachoCount(); if (contacto.isPressed()){ dos.writeInt(0); dos.flush(); Thread.sleep(500); } if (Math.abs(i) < 30){ if ((i > (ant_gir)) || (i < (ant_gir))){ dos.writeInt(4*i); dos.flush(); } }else if (Math.abs(i) < 60){ if ((i > (ant_gir + 1)) || (i < (ant_gir - 1))){ dos.writeInt(8*i); dos.flush(); } }else if (Math.abs(i) < 90){ if ((i > (ant_gir + 2)) || (i < (ant_gir - 2))){ dos.writeInt(16*i); dos.flush(); } }else{ if ((i > (ant_gir + 3)) || (i < (ant_gir - 3))){ dos.writeInt(32*i); dos.flush(); } } ant_gir = i; //Aceleración int j = Motor.C.getTachoCount(); if ((j > (ant)) || (j < (ant))){ float aux = j/10; j = (int) (aux * Math.abs(aux)*Math.abs(aux)); dos.writeInt(j + 1000); dos.flush(); } ant = j; } LCD.clear(); LCD.drawString("Cerrando conexion", 0, 1); dos.close(); btc.close(); LCD.clear(); LCD.drawString("Finalizado", 0, 1); Thread.sleep(2000); } } Y el del kart: import lejos.nxt.Motor; import lejos.robotics.navigation.Pilot; import lejos.robotics.navigation.TachoPilot; import lejos.nxt.Button; import lejos.nxt.LCD; import lejos.nxt.comm.BTConnection; import lejos.nxt.comm.Bluetooth; import java.io.*; import java.lang.Math; public class Kart { /** * @param args */ public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub Pilot navigator = new TachoPilot(6.8f, 17.8f, Motor.A, Motor.C, true); int limite = 120; LCD.clear(); LCD.drawString("Esperando...", 2, 1); LCD.refresh(); BTConnection btc = Bluetooth.waitForConnection(); LCD.clear(); LCD.drawString("Conectado", 2, 1); Motor.B.setPower(100); Motor.A.setPower(100); Motor.C.setPower(100); DataInputStream dis = btc.openDataInputStream(); while(!Button.ESCAPE.isPressed()){ int i = dis.readInt(); if (i == 0){ navigator.stop(); }else{ if (i > 1000){ //Acelerar navigator.forward(); navigator.setMoveSpeed((i - 1000)); }else if (i > 800){ navigator.backward(); navigator.setMoveSpeed(Math.abs(i - 1000)); }else{ //Girar Motor.B.stop(); boolean signo; if (i >= 0) signo = true; else signo = false; if (Math.abs(i) < limite){ Motor.B.rotateTo(-i, true); }else{ if (signo) Motor.B.rotateTo(-limite, true); else Motor.B.rotateTo(limite, true); } } } } Thread.sleep(2000); LCD.clear(); LCD.drawString("Cerrando conexion", 0, 1); dis.close(); btc.close(); Thread.sleep(2000); } }
Como podéis comprobar están los cambios comentados en el artículo, y el resultado ha sido bastante bueno. El único problema es que el manejo es algo complicado y se necesita práctica para conducirlo. Os recomiendo que miréis el artículo sobre la carrera. Finalmente deciros como siempre que si tenéis alguna duda podéis escribir vuestras dudas en el foro.