Tutoriales

Bluetooth y J2ME - Conectando un celular con la PC

Después de muchas búsquedas en la internet logré encontrar un ejemplo en el que se usa J2ME (Java Micro Edition) para manejar el puerto serial virtual Bluetooth de un celular y conectarlo a otro puerto serial ubicado en otro dispositivo Bluetooth, pudiendo ser éste una PC, otro celular o incluso dispositivos especiales como un Roomba.

El proyecto se llama RoombaCtrl: Drive your Roomba with your cell phone. El creador modificó y agregó un adaptador Bluetooth a su Roomba y mediante tecnología J2ME escribió un programa para su celular Nokia con el que envía comandos de movimiento al limpiador mediante Bluetooth.

Este tutorial dará paso a paso las instrucciones para compilar un programa en J2ME que sea capaz de ejecutarse en cualquier celular y que se conecte al puerto serial de cualquier otro dispositivo. El objetivo específico del programa es enviar el número de la tecla que el usuario haya presionado en el celular. El programa se llamará Numbers v0.1.

 Software Requerido

- Java Development Kit JDK 6 Update 13
- Java Sun Wireless Toolkit 2.5.2_01 for CLDC
- Mobile Processing 0007

Usando Google puedes encontrar sus sitios de descarga oficial.

Items Requeridos

- Un celular que ejecute J2ME y que cuente con Bluetooth. Debe cumplir con la especificación JSR82.
- Otro dispositivo que cuente con Bluetooth y el perfil de puerto serial virtual

Una PC con Windows y un adaptador bluetooth son suficientes para recibir la información que el celular envíe.

Código fuente requerido

- Al final de la página lo puedes encontrar

 



Nota importante
Si deseas tomar un curso puedes encontrar links útiles sobre Java Micro Edition (J2ME) en el menú de patrocinadores a la izquierda o en el link superior.

 
Manos a la obra

Primero instala el JDK y después el Wireless Toolkit. Estos programas son los encargados de proveer las librerías y compiladores comunes para convertir el código fuente (archivo.java) en el byte code que se interpretará por la pequeña máquina virtual del celular.

Normalmente los expertos en J2ME solo requieren los dos paquetes anteriores para compilar aplicaciones robustas para celular, pero los novatos requerimos que se nos facilite la vida y para ello existe Mobile Processing. Este programa es un ambiente de desarrollo gratuito que facilita la creación, compilación y emulación de programas J2ME, haciendo uso del JDK y el toolkit inalámbrico.

Los proyectos se guardan como bosquejos (sketches) con extensión .pde. Puedes analizar el proyecto de RoombaCtrl descargando el archivo roombactrl.pde de la página del creador y abriéndolo con Mobile Processing.

Para poder compilarlo tendrás que descargar la librería Bluetooth que Tod modificó para que funcionara con identificadores cortos. El procedimiento es el siguiente:

- Descarga el archivo mobileprocessing-bluetooth-patched.zip de la página del proyecto
- Descomprime el archivo en la carpeta libraries de la carpeta de Mobile Processing. Sobreescribe todo.

Después deberás abrir el archivo pde y dar click en el botón Play de la ventana. Si instalaste correctamente la librería Bluetooth modificada, se abrirá el emulador del toolkit ejecutando la aplicación RoombaCtrl. No podrás probar el funcionamiento Bluetooth del programa pero sí podrás ver su aspecto estético antes de cargarlo en tu celular.

 

  

 

 
Escribiendo una aplicación mucho más simple

El proyecto RoombaCtrl se ve muy complejo pero en realidad no lo es. Se puede observar en el código que el programa se ejecuta como si fuera una máquina de estados. Para mi programa Numbers v0.1 los pasos requeridos para entablar la conexión son los siguientes:

- Estado de inicio. Presenta el programa al usuario y espera a que presione cualquier tecla.
- Estado de búsqueda. Busca todos los dispositivos Bluetooth que hay a su alrededor. Al encontrarlos verifica si cuentan con el perfil de puerto serial virtual (RFCOMM). Enumera en una lista los dispositivos verificados y los presenta al usuario.
- Estado conectado. El usuario elegirá un puerto serial de los dispositivos encontrados. El programa intentará entablar una conexión. Si no hubiera éxito, la máquina virtual arrojará alguna excepción. Si el servicio de puerto serie se encontró es poco probable que no se pueda hacer la conexión.

Podrás ver en el código fuente varias funciones: setup, destroy, draw, keyPressed y libraryEvent.

- Setup. Esta función se ejecuta al arrancar la aplicación. La puedes usar para instanciar objetos.
- Destroy. Se ejecuta al terminar la aplicación.
- Draw. Se ejecuta automáticamente cada cierto tiempo. No hay que invocarla manualmente.
- KeyPressed. Se ejecuta cuando el usuario presiona un botón del teclado.
- LibraryEvent. Se ejecuta cuando alguna librería detona un evento.

La función más importante de la aplicación es libraryEvent ya que es la que indica si se han encontrado dispositivos y si cuentan con puertos serie disponibles. Dentro de ella se acumulan los perfiles encontrados y se crea un mensaje que informa al usuario el progreso de la búsqueda (variable mens).

Puntos clave del código:

- El inicio de la búsqueda se logra ejecutando el método Find del objeto bt: bt.Find().
- La conexión se establece seleccionando un servicio de la lista de encontrados e invocando el método connect: c = servicios[i].connect().
- El envío de un caracter se hace con el comando write del objeto cliente: c.write('a'). Aparentemente se debe limpiar el buffer después de ejecutar el envío, yo removí el flush y el envío dejó de funcionar.

Una vez terminado el código podrás compilar el programa con el menú Sketch -> Build and Run. Al compilarlo correctamente se abrirá el emulador del toolkit mostrando tu aplicación y se generará el tan codiciado archivo .JAR para tu celular dentro de la carpeta midlet.


Puesta a prueba

- En tu PC ejecuta el software encargado de administrar las funciones Bluetooth. En mi caso uso IVT Bluesoleil y un dongle ISSCEDRBTA con su driver original.
- Verifica en la ventana principal de tu software qué puertos seriales virtuales son los que se destinaron a Bluetooth. En mi caso son COM7 y COM8.
- Verifica que el software en tu PC tenga habilitada la opción 'Permitir que otros dispositivos Bluetooth encuentren este dispositivo'.
- Instala el archivo JAR generado en tu celular y ejecútalo.
- Al terminar la búsqueda deberá aparecer el nombre de tu PC o el nombre que hayas asignado a tu Bluetooth y una lista de números. Presiona el número que coincida con tu PC.
- Aquí se intentará entablar la conexión. De ser exitosa te mostrará un mensaje confimándolo.
- En tu PC abre la hyperterminal o cualquier otro programa similar. Conéctate al primer puerto serial que esté destinado a Bluetooth.
- En tu celular presiona cualquier número y observa cómo se envía a la hyperterminal... inalámbricamente :D
- Si no ves nada, prueba conectar la Hyperterminal al siguiente puerto Bluetooth disponible.


Bueno, así termina este tutorial. Por ahora solo he probado esta aplicación en mi Sony Ericsson K550i y con Windows 7 corriendo IVT Bluesoleil.


Si deseas modificar el programa para recibir datos desde otro dispositivo Bluetooth lo puedes hacer con el comando c.read(). Para más detalles al respecto lee la documentación de Mobile Processing sobre su librería Bluetooth.



A continuación dejo el código fuente del programa.

 /*
Numbers v0.1

Programa para J2ME que envía números presionados en el celular a un puerto
serial Bluetooth en una PC o cualquier otro sistema Bluetooth.

Autor: Santiago Villafuerte
www.migsantiago.com

Basado en el proyecto RoombaCtrl, http://roombahacking.com
Compilado con JDK 6.13, Wireless Toolkit 2.5.2_01 for CLDC y Mobile Processing 0007
http://mobile.processing.org
*/

//Usa la librería modificada por Todbot RoombaCtrl
import processing.bluetooth.*;

//Máquina de estados
final int ESTADO_INICIO = 0;
final int ESTADO_BUSCA = 1;
final int ESTADO_CONECTADO = 2;

int estado;

//Librería bluetooth
Bluetooth bt;
//Servicios descubiertos
Service[] servicios;
//Mensaje de estado
String mens;
//Conexión serial
Client c;
//Fuente
PFont font;

///////////////////////////////////////////////////////////////////
void setup()
{
  font=loadFont();
  textFont(font);
 
  //Inicializa objeto Bluetooth
  bt = new Bluetooth(this, Bluetooth.UUID_SERIALPORT);
 
  estado=ESTADO_INICIO;  
  ellipseMode(CORNER);
}
///////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////
void destroy()
{
  bt.stop();
}
///////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////
void draw()
{
  background(0xFF, 0xFF, 0xFF); //RGB - blanco
 
  //Dibuja la pantalla dependiendo de qué estado tiene la máquina
  switch(estado)
  {
    case ESTADO_INICIO:
      fill(0);
      textAlign(CENTER);
      text("Numbers v0.1\nSantiago Villafuerte\nmigsantiago.com\nPresione una tecla\n",2,2,width-4,height-4);
      break;
      
    case ESTADO_BUSCA:
      fill(0);
      textAlign(LEFT);
      //Si no se han encontrado servicios
      if(servicios==null)
        text("Numbers v0.1\nBúsqueda\n"+mens,2,2,width-4,height-4);
      //Si ya hay al menos un servicio
      else
        {
        String lista = "Elija un puerto serial:\n";
        for(int i=0, conteo = length(servicios); i<conteo;++i)
          lista += i +".- "+servicios[i].device.name+"\n"; //Hace una lista con nombres de dispositivos hallados
        text(lista,2,2,width-4,height-4);
        }
      break;
      
    case ESTADO_CONECTADO:
      fill(0);
      text("Numbers v0.1\n"+
        "Presione números del 0 al 9 para enviarlos a la PC\n"+mens,2,2,width-4,height-4);
      break;
  }
}
///////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////
void keyPressed()
{
  //Máquina de estados
  switch(estado)
  {
    case ESTADO_INICIO:
      servicios=null; //Limpia los servicios
      bt.find(); //Comienza la búsqueda de dispositivos bluetooth
      estado=ESTADO_BUSCA;
      mens="Buscando dispositivos Bluetooth...\n";
      break;
      
    case ESTADO_BUSCA:
      //Si ya hay al menos un dispositivo encontrado
      if(servicios!=null)
      {
        //Verifica que haya presionado algo del 0 al 9
        if((key>='0') && (key<='9'))
        {
          int i = key - '0'; //Conversión ASCII a entero
          //Si la tecla presionada existe como puerto disponible
          if(i < length(servicios))
          {
            mens="Conectando...";
            c = servicios[i].connect();
            estado= ESTADO_CONECTADO;
            mens="Conexión exitosa :D";
          }
        }
      }
      break;
      
    case ESTADO_CONECTADO:
      //Envía por el puerto serial la tecla que el usuario presionó
      c.write(key);
      c.flush();
      mens="ASCII: '"+key+ "' Entero: " + (int)key;
      break;
  }
}
///////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////
//Aquí llega cuando alguna librería genera un evento
void libraryEvent(Object library, int event, Object data)
{
  //Si la librería no fue bluetooth, sal de aquí
  if(library!=bt)
    return;
  switch(event)
  {
    //Se encontró un dispositivo
    case Bluetooth.EVENT_DISCOVER_DEVICE:
      mens="Dispositivo: " + ((Device)data).address;
      break;
      
    //Ya se encontraron todos los dispositivos posibles
    case Bluetooth.EVENT_DISCOVER_DEVICE_COMPLETED:
      mens="Se encontraron " + length((Device[])data) + " dispositivos,\n" +
        "buscando puertos seriales...";
      break;
      
    //Encontró un puerto serial
    case Bluetooth.EVENT_DISCOVER_SERVICE:
      mens="Puerto serial en " + ((Service[])data)[0].device.address;
      break;
      
    //Búsqueda de puertos seriales terminada
    case Bluetooth.EVENT_DISCOVER_SERVICE_COMPLETED:
      servicios=(Service[])data; //Junta todos los puertos encontrados en un arreglo
      mens="Búsqueda terminada. Elija un puerto";
      break;
      
    //Ya se conectó al cliente
    case Bluetooth.EVENT_CLIENT_CONNECTED:
      c=(Client)data;
      mens="Conexión exitosa :D";
      break;
  }
}
///////////////////////////////////////////////////////////////////